├── style ├── base.css ├── index.js └── index.css ├── .prettierrc ├── .eslintignore ├── .prettierignore ├── .binder ├── postBuild └── environment.yml ├── jupyter_config.json ├── jupyter-config ├── jupyter_notebook_config.d │ └── jupyterlab_link_share.json └── jupyter_server_config.d │ └── jupyterlab_link_share.json ├── install.json ├── jupyterlab_link_share ├── _version.py ├── __init__.py └── handlers.py ├── MANIFEST.in ├── tsconfig.json ├── pyproject.toml ├── .eslintrc.js ├── src ├── handler.ts └── index.ts ├── setup.cfg ├── LICENSE ├── .github └── workflows │ ├── check-release.yml │ └── build.yml ├── setup.py ├── .gitignore ├── README.md ├── package.json └── CHANGELOG.md /style/base.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /style/index.js: -------------------------------------------------------------------------------- 1 | import './base.css'; 2 | -------------------------------------------------------------------------------- /style/index.css: -------------------------------------------------------------------------------- 1 | @import url('base.css'); 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage 4 | **/*.d.ts 5 | tests 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | **/node_modules 3 | **/lib 4 | **/package.json 5 | -------------------------------------------------------------------------------- /.binder/postBuild: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | python -m pip install . 5 | -------------------------------------------------------------------------------- /jupyter_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "LabApp": { "collaborative": true }, 3 | "RetroApp": { "collaborative": true } 4 | } 5 | -------------------------------------------------------------------------------- /jupyter-config/jupyter_notebook_config.d/jupyterlab_link_share.json: -------------------------------------------------------------------------------- 1 | { 2 | "NotebookApp": { 3 | "nbserver_extensions": { 4 | "jupyterlab_link_share": true 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /jupyter-config/jupyter_server_config.d/jupyterlab_link_share.json: -------------------------------------------------------------------------------- 1 | { 2 | "ServerApp": { 3 | "jpserver_extensions": { 4 | "jupyterlab_link_share": true 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.binder/environment.yml: -------------------------------------------------------------------------------- 1 | name: jupyterlab-link-share 2 | channels: 3 | - conda-forge 4 | dependencies: 5 | - jupyterlab>=3.5,<4 6 | - retrolab>=0.3.13,<0.4 7 | - numpy 8 | - nodejs=14 9 | - python >=3.9,<3.10 10 | -------------------------------------------------------------------------------- /install.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageManager": "python", 3 | "packageName": "jupyterlab-link-share", 4 | "uninstallInstructions": "Use your Python package manager (pip, conda, etc.) to uninstall the package jupyterlab-link-share" 5 | } 6 | -------------------------------------------------------------------------------- /jupyterlab_link_share/_version.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | 4 | __all__ = ["__version__"] 5 | 6 | def _fetchVersion(): 7 | HERE = Path(__file__).parent.resolve() 8 | 9 | for settings in HERE.rglob("package.json"): 10 | try: 11 | with settings.open() as f: 12 | return json.load(f)["version"] 13 | except FileNotFoundError: 14 | pass 15 | 16 | raise FileNotFoundError(f"Could not find package.json under dir {HERE!s}") 17 | 18 | __version__ = _fetchVersion() 19 | 20 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include *.md 3 | include pyproject.toml 4 | include jupyter-config/jupyter_server_config.d/jupyterlab_link_share.json 5 | include jupyter-config/jupyter_notebook_config.d/jupyterlab_link_share.json 6 | 7 | include package.json 8 | include install.json 9 | include ts*.json 10 | 11 | graft jupyterlab_link_share/labextension 12 | 13 | # Javascript files 14 | graft src 15 | graft style 16 | prune **/node_modules 17 | prune lib 18 | 19 | # Patterns to exclude from any directory 20 | global-exclude *~ 21 | global-exclude *.pyc 22 | global-exclude *.pyo 23 | global-exclude .git 24 | global-exclude .ipynb_checkpoints 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "composite": true, 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "incremental": true, 8 | "jsx": "react", 9 | "module": "esnext", 10 | "moduleResolution": "node", 11 | "noEmitOnError": true, 12 | "noImplicitAny": true, 13 | "noUnusedLocals": true, 14 | "preserveWatchOutput": true, 15 | "resolveJsonModule": true, 16 | "outDir": "lib", 17 | "rootDir": "src", 18 | "strict": true, 19 | "strictNullChecks": false, 20 | "target": "es2018", 21 | "types": [] 22 | }, 23 | "include": ["src/*"] 24 | } 25 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["jupyter_packaging~=0.10", "jupyterlab~=3.0"] 3 | build-backend = "jupyter_packaging.build_api" 4 | 5 | [license] 6 | file="LICENSE" 7 | 8 | [tool.jupyter-packaging.options] 9 | skip-if-exists = ["jupyterlab_link_share/labextension/static/style.js"] 10 | ensured-targets = ["jupyterlab_link_share/labextension/static/style.js", "jupyterlab_link_share/labextension/package.json"] 11 | 12 | [tool.jupyter-packaging.builder] 13 | factory = "jupyter_packaging.npm_builder" 14 | 15 | [tool.jupyter-packaging.build-args] 16 | build_cmd = "build:prod" 17 | npm = ["jlpm"] 18 | 19 | [tool.check-manifest] 20 | ignore = [".binder/**", "*.json", "yarn.lock", ".*", "jupyterlab_link_share/labextension/**"] 21 | -------------------------------------------------------------------------------- /jupyterlab_link_share/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | import json 3 | from pathlib import Path 4 | 5 | from .handlers import setup_handlers 6 | from ._version import __version__ 7 | 8 | HERE = Path(__file__).parent.resolve() 9 | 10 | with (HERE / "labextension" / "package.json").open() as fid: 11 | data = json.load(fid) 12 | 13 | 14 | def _jupyter_labextension_paths(): 15 | return [{ 16 | "src": "labextension", 17 | "dest": data["name"] 18 | }] 19 | 20 | 21 | def _jupyter_server_extension_points(): 22 | return [{ 23 | "module": "jupyterlab_link_share" 24 | }] 25 | 26 | 27 | def _load_jupyter_server_extension(server_app): 28 | setup_handlers(server_app.web_app) 29 | server_app.log.info("Registered JupyterLab Link Share extension at URL path /jupyterlab_link_share") 30 | 31 | load_jupyter_server_extension = _load_jupyter_server_extension 32 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'eslint:recommended', 4 | 'plugin:@typescript-eslint/eslint-recommended', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:prettier/recommended' 7 | ], 8 | parser: '@typescript-eslint/parser', 9 | parserOptions: { 10 | project: 'tsconfig.json', 11 | sourceType: 'module' 12 | }, 13 | plugins: ['@typescript-eslint'], 14 | rules: { 15 | '@typescript-eslint/interface-name-prefix': [ 16 | 'error', 17 | { prefixWithI: 'always' } 18 | ], 19 | '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], 20 | '@typescript-eslint/no-explicit-any': 'off', 21 | '@typescript-eslint/no-namespace': 'off', 22 | '@typescript-eslint/no-use-before-define': 'off', 23 | '@typescript-eslint/quotes': [ 24 | 'error', 25 | 'single', 26 | { avoidEscape: true, allowTemplateLiterals: false } 27 | ], 28 | curly: ['error', 'all'], 29 | eqeqeq: 'error', 30 | 'prefer-arrow-callback': 'error' 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /jupyterlab_link_share/handlers.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import tornado 4 | 5 | from jupyter_server.base.handlers import APIHandler 6 | from jupyter_server.utils import url_path_join 7 | from jupyter_server.serverapp import list_running_servers as list_jupyter_servers 8 | 9 | list_notebook_servers = None 10 | 11 | try: 12 | from notebook.notebookapp import list_running_servers as list_notebook_servers 13 | except: 14 | pass 15 | 16 | 17 | class RouteHandler(APIHandler): 18 | @tornado.web.authenticated 19 | def get(self): 20 | servers = list(list_jupyter_servers()) 21 | if list_notebook_servers: 22 | servers += list(list_notebook_servers()) 23 | # sort by pid so PID 1 is first in Docker and Binder 24 | servers.sort(key=lambda x: x["pid"]) 25 | self.finish(json.dumps(servers)) 26 | 27 | 28 | def setup_handlers(web_app): 29 | host_pattern = ".*$" 30 | 31 | base_url = web_app.settings["base_url"] 32 | route_pattern = url_path_join(base_url, "jupyterlab_link_share", "servers") 33 | handlers = [(route_pattern, RouteHandler)] 34 | web_app.add_handlers(host_pattern, handlers) 35 | -------------------------------------------------------------------------------- /src/handler.ts: -------------------------------------------------------------------------------- 1 | import { URLExt } from '@jupyterlab/coreutils'; 2 | 3 | import { ServerConnection } from '@jupyterlab/services'; 4 | 5 | /** 6 | * Call the API extension 7 | * 8 | * @param endPoint API REST end point for the extension 9 | * @param init Initial values for the request 10 | * @returns The response body interpreted as JSON 11 | */ 12 | export async function requestAPI( 13 | endPoint = '', 14 | init: RequestInit = {} 15 | ): Promise { 16 | const settings = ServerConnection.makeSettings(); 17 | const requestUrl = URLExt.join( 18 | settings.baseUrl, 19 | 'jupyterlab_link_share', 20 | endPoint 21 | ); 22 | 23 | let response: Response; 24 | try { 25 | response = await ServerConnection.makeRequest(requestUrl, init, settings); 26 | } catch (error) { 27 | throw new ServerConnection.NetworkError(error); 28 | } 29 | 30 | let data: any = await response.text(); 31 | 32 | if (data.length > 0) { 33 | try { 34 | data = JSON.parse(data); 35 | } catch (error) { 36 | console.log('Not a JSON response body.', response); 37 | } 38 | } 39 | 40 | if (!response.ok) { 41 | throw new ServerConnection.ResponseError(response, data.message || data); 42 | } 43 | 44 | return data; 45 | } 46 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = jupyterlab-link-share 3 | description = JupyterLab Extension to share the URL to a running Jupyter Server 4 | long_description = file: README.md 5 | long_description_content_type = text/markdown 6 | license_file = LICENSE 7 | author = Projet Jupyter Contributors 8 | url = https://github.com/jupyterlab-contrib/jupyterlab-link-share 9 | platforms = Linux, Mac OS X, Windows 10 | keywords = Jupyter, JupyterLab, Widgets, IPython 11 | classifiers = 12 | Intended Audience :: Developers 13 | Intended Audience :: Science/Research 14 | License :: OSI Approved :: BSD License 15 | Programming Language :: Python 16 | Programming Language :: Python :: 3 17 | Programming Language :: Python :: 3.7 18 | Programming Language :: Python :: 3.8 19 | Programming Language :: Python :: 3.9 20 | Programming Language :: Python :: 3.10 21 | Programming Language :: Python :: 3.11 22 | Framework :: Jupyter 23 | Framework :: Jupyter :: JupyterLab 24 | Framework :: Jupyter :: JupyterLab :: 3 25 | Framework :: Jupyter :: JupyterLab :: Extensions 26 | Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt 27 | 28 | [options] 29 | zip_safe = False 30 | include_package_data = True 31 | packages = find: 32 | python_requires = >=3.7 33 | install_requires = 34 | jupyter_server>=1.11,<3 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Project Jupyter Contributors All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | * Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /.github/workflows/check-release.yml: -------------------------------------------------------------------------------- 1 | name: Check Release 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | 10 | permissions: 11 | contents: write 12 | 13 | jobs: 14 | check_release: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v2 19 | - name: Install Python 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: 3.9 23 | architecture: 'x64' 24 | - name: Install node 25 | uses: actions/setup-node@v2 26 | with: 27 | node-version: '14.x' 28 | - name: Get pip cache dir 29 | id: pip-cache 30 | run: | 31 | echo "::set-output name=dir::$(pip cache dir)" 32 | - name: Cache pip 33 | uses: actions/cache@v1 34 | with: 35 | path: ${{ steps.pip-cache.outputs.dir }} 36 | key: ${{ runner.os }}-pip-${{ hashFiles('setup.cfg', 'setup.py') }} 37 | restore-keys: | 38 | ${{ runner.os }}-pip- 39 | - name: Cache checked links 40 | uses: actions/cache@v2 41 | with: 42 | path: ~/.cache/pytest-link-check 43 | key: ${{ runner.os }}-linkcheck-${{ hashFiles('**/.md') }}-md-links 44 | restore-keys: | 45 | ${{ runner.os }}-linkcheck- 46 | - name: Upgrade packaging dependencies 47 | run: | 48 | pip install --upgrade pip setuptools wheel jupyter-packaging~=0.10 --user 49 | - name: Install Dependencies 50 | run: | 51 | pip install . 52 | - name: Check Release 53 | uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v1 54 | with: 55 | token: ${{ secrets.GITHUB_TOKEN }} 56 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: main 6 | pull_request: 7 | branches: '*' 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | 16 | - name: Install node 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: '14.x' 20 | 21 | - name: Install Python 22 | uses: actions/setup-python@v2 23 | with: 24 | python-version: '3.9' 25 | architecture: 'x64' 26 | 27 | - name: Setup pip cache 28 | uses: actions/cache@v2 29 | with: 30 | path: ~/.cache/pip 31 | key: pip-3.9-${{ hashFiles('setup.cfg') }} 32 | restore-keys: | 33 | pip-3.9- 34 | pip- 35 | 36 | - name: Get yarn cache directory path 37 | id: yarn-cache-dir-path 38 | run: echo "::set-output name=dir::$(yarn cache dir)" 39 | 40 | - name: Setup yarn cache 41 | uses: actions/cache@v2 42 | id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) 43 | with: 44 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 45 | key: yarn-${{ hashFiles('**/yarn.lock') }} 46 | restore-keys: | 47 | yarn- 48 | 49 | - name: Install dependencies 50 | run: python -m pip install jupyterlab 51 | - name: Build the extension 52 | run: | 53 | jlpm 54 | jlpm run eslint:check 55 | python -m pip install . 56 | 57 | jupyter labextension list 2>&1 | grep -ie "jupyterlab-link-share.*OK" 58 | jupyter server extension list 2>&1 | grep -ie "jupyterlab_link_share.*OK" 59 | jupyter serverextension list 2>&1 | grep -ie "jupyterlab_link_share.*OK" 60 | 61 | python -m jupyterlab.browser_check 62 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | jupyterlab-link-share setup 3 | """ 4 | import json 5 | from pathlib import Path 6 | 7 | import setuptools 8 | 9 | HERE = Path(__file__).parent.resolve() 10 | 11 | # The name of the project 12 | name = "jupyterlab-link-share" 13 | package = name.replace('-', '_') 14 | 15 | lab_path = HERE / package / "labextension" 16 | 17 | # Representative files that should exist after a successful build 18 | ensured_targets = [ 19 | str(lab_path / "package.json"), 20 | str(lab_path / "static" / "style.js"), 21 | ] 22 | 23 | package_data_spec = {package: ["*"]} 24 | 25 | labext_name = "jupyterlab-link-share" 26 | 27 | data_files_spec = [ 28 | ("share/jupyter/labextensions/%s" % labext_name, str(lab_path), "**"), 29 | ("share/jupyter/labextensions/%s" % labext_name, str(HERE), "install.json"), 30 | ( 31 | "etc/jupyter/jupyter_server_config.d", 32 | "jupyter-config/jupyter_server_config.d", 33 | "jupyterlab_link_share.json", 34 | ), 35 | ( 36 | "etc/jupyter/jupyter_notebook_config.d", 37 | "jupyter-config/jupyter_notebook_config.d", 38 | "jupyterlab_link_share.json", 39 | ), 40 | ] 41 | 42 | 43 | # Get the package info from package.json 44 | pkg_json = json.loads((HERE / "package.json").read_bytes()) 45 | 46 | try: 47 | from jupyter_packaging import wrap_installers, npm_builder, get_data_files 48 | 49 | # In develop mode, just run yarn 50 | builder = npm_builder(build_cmd="build", build_dir=lab_path, source_dir="src") 51 | cmdclass = wrap_installers(post_develop=builder, ensured_targets=ensured_targets) 52 | 53 | setup_args = dict( 54 | cmdclass=cmdclass, 55 | data_files=get_data_files(data_files_spec), 56 | ) 57 | except ImportError: 58 | setup_args = dict() 59 | 60 | 61 | setup_args["version"] = pkg_json["version"] 62 | 63 | if __name__ == "__main__": 64 | setuptools.setup(**setup_args) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.bundle.* 2 | lib/ 3 | node_modules/ 4 | *.egg-info/ 5 | .ipynb_checkpoints 6 | *.tsbuildinfo 7 | jupyterlab_link_share/labextension 8 | 9 | # Created by https://www.gitignore.io/api/python 10 | # Edit at https://www.gitignore.io/?templates=python 11 | 12 | ### Python ### 13 | # Byte-compiled / optimized / DLL files 14 | __pycache__/ 15 | *.py[cod] 16 | *$py.class 17 | 18 | # C extensions 19 | *.so 20 | 21 | # Distribution / packaging 22 | .Python 23 | build/ 24 | develop-eggs/ 25 | dist/ 26 | downloads/ 27 | eggs/ 28 | .eggs/ 29 | lib/ 30 | lib64/ 31 | parts/ 32 | sdist/ 33 | var/ 34 | wheels/ 35 | pip-wheel-metadata/ 36 | share/python-wheels/ 37 | .installed.cfg 38 | *.egg 39 | MANIFEST 40 | 41 | # PyInstaller 42 | # Usually these files are written by a python script from a template 43 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 44 | *.manifest 45 | *.spec 46 | 47 | # Installer logs 48 | pip-log.txt 49 | pip-delete-this-directory.txt 50 | 51 | # Unit test / coverage reports 52 | htmlcov/ 53 | .tox/ 54 | .nox/ 55 | .coverage 56 | .coverage.* 57 | .cache 58 | nosetests.xml 59 | coverage.xml 60 | *.cover 61 | .hypothesis/ 62 | .pytest_cache/ 63 | 64 | # Translations 65 | *.mo 66 | *.pot 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # celery beat schedule file 81 | celerybeat-schedule 82 | 83 | # SageMath parsed files 84 | *.sage.py 85 | 86 | # Spyder project settings 87 | .spyderproject 88 | .spyproject 89 | 90 | # Rope project settings 91 | .ropeproject 92 | 93 | # Mr Developer 94 | .mr.developer.cfg 95 | .project 96 | .pydevproject 97 | 98 | # mkdocs documentation 99 | /site 100 | 101 | # mypy 102 | .mypy_cache/ 103 | .dmypy.json 104 | dmypy.json 105 | 106 | # Pyre type checker 107 | .pyre/ 108 | 109 | # End of https://www.gitignore.io/api/python 110 | 111 | # OSX files 112 | .DS_Store 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jupyterlab-link-share 2 | 3 | [![Extension status](https://img.shields.io/badge/status-ready-success "ready to be used")](https://jupyterlab-contrib.github.io/) 4 | [![Github Actions Status](https://github.com/jupyterlab-contrib/jupyterlab-link-share/workflows/Build/badge.svg)](https://github.com/jupyterlab-contrib/jupyterlab-link-share/actions/workflows/build.yml) 5 | [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/jupyterlab-contrib/jupyterlab-link-share/main?urlpath=/lab) 6 | 7 | JupyterLab Extension to share the URL to a running Jupyter Server 8 | 9 | ![screencast](https://user-images.githubusercontent.com/591645/104604669-e0f53880-567d-11eb-989f-2bf2edd416ce.gif) 10 | 11 | ## Requirements 12 | 13 | * JupyterLab >= 3.0 14 | 15 | ## Install 16 | 17 | ```bash 18 | pip install jupyterlab-link-share 19 | ``` 20 | 21 | ## Contributing 22 | 23 | ### Development install 24 | 25 | Note: You will need NodeJS to build the extension package. 26 | 27 | The `jlpm` command is JupyterLab's pinned version of 28 | [yarn](https://yarnpkg.com/) that is installed with JupyterLab. You may use 29 | `yarn` or `npm` in lieu of `jlpm` below. 30 | 31 | ```bash 32 | # Clone the repo to your local environment 33 | # Change directory to the jupyterlab-link-share directory 34 | # Install package in development mode 35 | pip install -e . 36 | # Link your development version of the extension with JupyterLab 37 | jupyter labextension develop . --overwrite 38 | # Rebuild extension Typescript source after making changes 39 | jlpm run build 40 | ``` 41 | 42 | You can watch the source directory and run JupyterLab at the same time in different terminals to watch for changes in the extension's source and automatically rebuild the extension. 43 | 44 | ```bash 45 | # Watch the source directory in one terminal, automatically rebuilding when needed 46 | jlpm run watch 47 | # Run JupyterLab in another terminal 48 | jupyter lab 49 | ``` 50 | 51 | With the watch command running, every saved change will immediately be built locally and available in your running JupyterLab. Refresh JupyterLab to load the change in your browser (you may need to wait several seconds for the extension to be rebuilt). 52 | 53 | By default, the `jlpm run build` command generates the source maps for this extension to make it easier to debug using the browser dev tools. To also generate source maps for the JupyterLab core extensions, you can run the following command: 54 | 55 | ```bash 56 | jupyter lab build --minimize=False 57 | ``` 58 | 59 | ### Uninstall 60 | 61 | ```bash 62 | pip uninstall jupyterlab-link-share 63 | ``` 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jupyterlab-link-share", 3 | "version": "0.3.0", 4 | "description": "JupyterLab Extension to share the URL to a running Jupyter Server", 5 | "keywords": [ 6 | "jupyter", 7 | "jupyterlab", 8 | "jupyterlab-extension" 9 | ], 10 | "homepage": "https://github.com/jupyterlab-contrib/jupyterlab-link-share", 11 | "bugs": { 12 | "url": "https://github.com/jupyterlab-contrib/jupyterlab-link-share/issues" 13 | }, 14 | "license": "BSD-3-Clause", 15 | "author": "Project Jupyter Contributors", 16 | "files": [ 17 | "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}", 18 | "style/**/*.{css,.js,eot,gif,html,jpg,json,png,svg,woff2,ttf}" 19 | ], 20 | "main": "lib/index.js", 21 | "types": "lib/index.d.ts", 22 | "style": "style/index.css", 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/jupyterlab-contrib/jupyterlab-link-share.git" 26 | }, 27 | "scripts": { 28 | "build": "jlpm run build:lib && jlpm run build:labextension:dev", 29 | "build:prod": "jlpm run build:lib && jlpm run build:labextension", 30 | "build:labextension": "jupyter labextension build .", 31 | "build:labextension:dev": "jupyter labextension build --development True .", 32 | "build:lib": "tsc", 33 | "clean": "jlpm run clean:lib", 34 | "clean:lib": "rimraf lib tsconfig.tsbuildinfo", 35 | "clean:labextension": "rimraf jupyterlab-link-share/labextension", 36 | "clean:all": "jlpm run clean:lib && jlpm run clean:labextension", 37 | "eslint": "eslint . --ext .ts,.tsx --fix", 38 | "eslint:check": "eslint . --ext .ts,.tsx", 39 | "install:extension": "jupyter labextension develop --overwrite .", 40 | "watch": "run-p watch:src watch:labextension", 41 | "watch:src": "tsc -w", 42 | "watch:labextension": "jupyter labextension watch ." 43 | }, 44 | "dependencies": { 45 | "@jupyterlab/application": "^3.0.0", 46 | "@jupyterlab/apputils": "^3.0.0", 47 | "@jupyterlab/coreutils": "^5.0.0", 48 | "@jupyterlab/mainmenu": "^3.0.0", 49 | "@jupyterlab/services": "^6.0.0", 50 | "@jupyterlab/translation": "^3.0.0", 51 | "@lumino/widgets": "^1.17.0", 52 | "@retrolab/application": "^0.3.13" 53 | }, 54 | "devDependencies": { 55 | "@jupyterlab/builder": "^3.0.0", 56 | "@typescript-eslint/eslint-plugin": "^2.27.0", 57 | "@typescript-eslint/parser": "^2.27.0", 58 | "eslint": "^7.5.0", 59 | "eslint-config-prettier": "^6.10.1", 60 | "eslint-plugin-prettier": "^3.1.2", 61 | "npm-run-all": "^4.1.5", 62 | "prettier": "^1.19.0", 63 | "rimraf": "^3.0.2", 64 | "typescript": "~4.1.3" 65 | }, 66 | "sideEffects": [ 67 | "style/*.css", 68 | "style/index.js" 69 | ], 70 | "styleModule": "style/index.js", 71 | "jupyterlab": { 72 | "extension": true, 73 | "outputDir": "jupyterlab_link_share/labextension" 74 | }, 75 | "jupyter-releaser": { 76 | "hooks": { 77 | "before-build-npm": [ 78 | "python -m pip install jupyterlab~=3.1", 79 | "jlpm" 80 | ] 81 | } 82 | }, 83 | "publishConfig": { 84 | "access": "public" 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | JupyterFrontEnd, 3 | JupyterFrontEndPlugin 4 | } from '@jupyterlab/application'; 5 | 6 | import { 7 | Clipboard, 8 | Dialog, 9 | ICommandPalette, 10 | showDialog 11 | } from '@jupyterlab/apputils'; 12 | 13 | import { IRetroShell } from '@retrolab/application'; 14 | 15 | import { PageConfig, URLExt } from '@jupyterlab/coreutils'; 16 | 17 | import { IMainMenu } from '@jupyterlab/mainmenu'; 18 | 19 | import { ITranslator, nullTranslator } from '@jupyterlab/translation'; 20 | 21 | import { Menu, Widget } from '@lumino/widgets'; 22 | 23 | import { requestAPI } from './handler'; 24 | 25 | /** 26 | * The command IDs used by the plugin. 27 | */ 28 | namespace CommandIDs { 29 | export const share = 'link-share:share'; 30 | } 31 | 32 | /** 33 | * Plugin to share the URL of the running Jupyter Server 34 | */ 35 | const plugin: JupyterFrontEndPlugin = { 36 | id: 'jupyterlab-link-share:plugin', 37 | autoStart: true, 38 | optional: [ICommandPalette, IMainMenu, ITranslator, IRetroShell], 39 | activate: async ( 40 | app: JupyterFrontEnd, 41 | palette: ICommandPalette | null, 42 | menu: IMainMenu | null, 43 | translator: ITranslator | null, 44 | retroShell: IRetroShell | null 45 | ) => { 46 | const { commands } = app; 47 | const trans = (translator ?? nullTranslator).load('jupyterlab'); 48 | 49 | commands.addCommand(CommandIDs.share, { 50 | label: trans.__('Share Jupyter Server Link'), 51 | execute: async () => { 52 | let results: { token: string }[]; 53 | const isRunningUnderJupyterHub = PageConfig.getOption('hubUser') !== ''; 54 | if (isRunningUnderJupyterHub) { 55 | // We are running on a JupyterHub, so let's just use the token set in PageConfig. 56 | // Any extra servers running on the server will still need to use this token anyway, 57 | // as all traffic (including any to jupyter-server-proxy) needs this token. 58 | results = [{ token: PageConfig.getToken() }]; 59 | } else { 60 | results = await requestAPI('servers'); 61 | } 62 | 63 | const links = results.map(server => { 64 | let url: URL; 65 | if (retroShell) { 66 | // On retrolab, take current URL and set ?token to it 67 | url = new URL(location.href); 68 | } else { 69 | // On JupyterLab, let PageConfig.getUrl do its magic. 70 | // Handles workspaces, single document mode, etc 71 | url = new URL( 72 | URLExt.normalize( 73 | `${PageConfig.getUrl({ 74 | workspace: PageConfig.defaultWorkspace 75 | })}` 76 | ) 77 | ); 78 | } 79 | const tokenURL = new URL(url.toString()); 80 | if (server.token) { 81 | // add token to URL 82 | tokenURL.searchParams.set('token', server.token); 83 | } 84 | return { 85 | noToken: url.toString(), 86 | withToken: tokenURL.toString() 87 | }; 88 | }); 89 | 90 | const dialogBody = document.createElement('div'); 91 | const entries = document.createElement('div'); 92 | dialogBody.appendChild(entries); 93 | links.map(link => { 94 | const p = document.createElement('p'); 95 | const text: HTMLInputElement = document.createElement('input'); 96 | text.dataset.noToken = link.noToken; 97 | text.dataset.withToken = link.withToken; 98 | text.readOnly = true; 99 | text.value = link.noToken; 100 | text.addEventListener('click', e => { 101 | (e.target as HTMLInputElement).select(); 102 | }); 103 | text.style.width = '100%'; 104 | p.appendChild(text); 105 | entries.appendChild(p); 106 | }); 107 | 108 | // Warn users of the security implications of using this link 109 | // FIXME: There *must* be a better way to create HTML 110 | const tokenWarning = document.createElement('div'); 111 | 112 | const warningHeader = document.createElement('h3'); 113 | warningHeader.innerText = trans.__('Security warning!'); 114 | tokenWarning.appendChild(warningHeader); 115 | 116 | const tokenMessages: Array = []; 117 | 118 | tokenMessages.push( 119 | 'Anyone with this link has full access to your notebook server, including all your files!', 120 | 'Please be careful who you share it with.' 121 | ); 122 | if (isRunningUnderJupyterHub) { 123 | tokenMessages.push( 124 | // You can restart the server to revoke the token in a JupyterHub 125 | 'They will be able to access this server AS YOU.' 126 | ); 127 | tokenMessages.push( 128 | // You can restart the server to revoke the token in a JupyterHub 129 | 'To revoke access, go to File -> Hub Control Panel, and restart your server' 130 | ); 131 | } else { 132 | tokenMessages.push( 133 | // Elsewhere, you *must* shut down your server - no way to revoke it 134 | 'Currently, there is no way to revoke access other than shutting down your server' 135 | ); 136 | } 137 | 138 | const noTokenMessage = document.createElement('div'); 139 | const noTokenMessages: Array = []; 140 | if (isRunningUnderJupyterHub) { 141 | noTokenMessages.push( 142 | 'Only users with `access:servers` permissions for this server will be able to use this link.' 143 | ); 144 | } else { 145 | noTokenMessages.push( 146 | 'Only authenticated users will be able to use this link.' 147 | ); 148 | } 149 | 150 | tokenMessages.map(m => { 151 | tokenWarning.appendChild(document.createTextNode(trans.__(m))); 152 | tokenWarning.appendChild(document.createElement('br')); 153 | }); 154 | noTokenMessages.map(m => { 155 | noTokenMessage.appendChild(document.createTextNode(trans.__(m))); 156 | noTokenMessage.appendChild(document.createElement('br')); 157 | }); 158 | const messages = { 159 | noToken: noTokenMessage, 160 | withToken: tokenWarning 161 | }; 162 | 163 | const message = document.createElement('div'); 164 | message.appendChild(messages.noToken); 165 | 166 | // whether there's any token to be used in URLs 167 | // if none, no point in adding a checkbox 168 | const hasToken = 169 | results.filter( 170 | server => server.token !== undefined && server.token !== '' 171 | ).length > 0; 172 | 173 | let includeTokenCheckbox: HTMLInputElement | undefined = undefined; 174 | if (hasToken) { 175 | // add checkbox to include token _if_ there's a token to include 176 | includeTokenCheckbox = document.createElement('input'); 177 | includeTokenCheckbox.type = 'checkbox'; 178 | const tokenLabel = document.createElement('label'); 179 | tokenLabel.appendChild(includeTokenCheckbox); 180 | tokenLabel.appendChild( 181 | document.createTextNode(trans.__('Include token in URL')) 182 | ); 183 | dialogBody.appendChild(tokenLabel); 184 | 185 | // when checkbox changes, toggle URL and message 186 | includeTokenCheckbox.addEventListener('change', e => { 187 | const isChecked: boolean = (e.target as HTMLInputElement).checked; 188 | const key = isChecked ? 'withToken' : 'noToken'; 189 | 190 | // add or remove the token to the URL inputs 191 | const inputElements = entries.getElementsByTagName('input'); 192 | [...inputElements].map(input => { 193 | input.value = input.dataset[key] as string; 194 | }); 195 | 196 | // swap out the warning message 197 | message.removeChild(message.children[0]); 198 | message.appendChild(messages[key]); 199 | }); 200 | } 201 | 202 | dialogBody.appendChild(message); 203 | 204 | const result = await showDialog({ 205 | title: trans.__('Share Jupyter Server Link'), 206 | body: new Widget({ node: dialogBody }), 207 | buttons: [ 208 | Dialog.cancelButton({ label: trans.__('Cancel') }), 209 | Dialog.okButton({ 210 | label: trans.__('Copy Link'), 211 | caption: trans.__('Copy the link to the Jupyter Server') 212 | }) 213 | ] 214 | }); 215 | if (result.button.accept) { 216 | const key = 217 | includeTokenCheckbox && includeTokenCheckbox.checked 218 | ? 'withToken' 219 | : 'noToken'; 220 | Clipboard.copyToSystem(links[0][key]); 221 | } 222 | } 223 | }); 224 | 225 | if (palette) { 226 | palette.addItem({ 227 | command: CommandIDs.share, 228 | category: trans.__('Server') 229 | }); 230 | } 231 | 232 | if (menu) { 233 | // Create a menu 234 | const shareMenu: Menu = new Menu({ commands }); 235 | shareMenu.title.label = trans.__('Share'); 236 | menu.addMenu(shareMenu, { rank: 10000 }); 237 | 238 | // Add the command to the menu 239 | shareMenu.addItem({ command: CommandIDs.share }); 240 | } 241 | } 242 | }; 243 | 244 | const plugins: JupyterFrontEndPlugin[] = [plugin]; 245 | export default plugins; 246 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | ## 0.3.0 6 | 7 | ([Full Changelog](https://github.com/jupyterlab-contrib/jupyterlab-link-share/compare/v0.2.5...d0a3f63d547881e438e68ee66a5a88f796eda90c)) 8 | 9 | ### Enhancements made 10 | 11 | - add dialog checkbox to include token in URL [#55](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/55) ([@minrk](https://github.com/minrk)) 12 | 13 | ### Other merged PRs 14 | 15 | - Bump webpack from 5.37.1 to 5.76.1 [#56](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/56) ([@dependabot](https://github.com/dependabot)) 16 | - Bump http-cache-semantics from 4.1.0 to 4.1.1 [#53](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/53) ([@dependabot](https://github.com/dependabot)) 17 | - Bump json5 from 1.0.1 to 1.0.2 [#52](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/52) ([@dependabot](https://github.com/dependabot)) 18 | 19 | ### Contributors to this release 20 | 21 | ([GitHub contributors page for this release](https://github.com/jupyterlab-contrib/jupyterlab-link-share/graphs/contributors?from=2022-12-03&to=2023-03-17&type=c)) 22 | 23 | [@dependabot](https://github.com/search?q=repo%3Ajupyterlab-contrib%2Fjupyterlab-link-share+involves%3Adependabot+updated%3A2022-12-03..2023-03-17&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyterlab-contrib%2Fjupyterlab-link-share+involves%3Ajtpio+updated%3A2022-12-03..2023-03-17&type=Issues) | [@minrk](https://github.com/search?q=repo%3Ajupyterlab-contrib%2Fjupyterlab-link-share+involves%3Aminrk+updated%3A2022-12-03..2023-03-17&type=Issues) 24 | 25 | 26 | 27 | ## 0.2.5 28 | 29 | ([Full Changelog](https://github.com/jupyterlab-contrib/jupyterlab-link-share/compare/v0.2.4...3fbd165391db6ee7f563bc42a2190179b7dec339)) 30 | 31 | ### Maintenance and upkeep improvements 32 | 33 | - Bump minimist from 1.2.5 to 1.2.6 [#39](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/39) ([@dependabot](https://github.com/dependabot)) 34 | - Bump url-parse from 1.5.7 to 1.5.10 [#38](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/38) ([@dependabot](https://github.com/dependabot)) 35 | - Bump node-fetch from 2.6.1 to 2.6.7 [#37](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/37) ([@dependabot](https://github.com/dependabot)) 36 | - Bump url-parse from 1.5.3 to 1.5.7 [#36](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/36) ([@dependabot](https://github.com/dependabot)) 37 | - Turn on collaborative mode on Binder [#32](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/32) ([@jtpio](https://github.com/jtpio)) 38 | - Add .binder to the check manifest ignore list [#31](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/31) ([@jtpio](https://github.com/jtpio)) 39 | - Add Binder files to be able to try with RetroLab [#30](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/30) ([@jtpio](https://github.com/jtpio)) 40 | 41 | ### Other merged PRs 42 | 43 | - Remove `prepare` fix [#50](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/50) ([@jtpio](https://github.com/jtpio)) 44 | - Relax the `jupyter_server` dependency [#47](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/47) ([@jtpio](https://github.com/jtpio)) 45 | - Bump loader-utils from 1.4.1 to 1.4.2 [#46](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/46) ([@dependabot](https://github.com/dependabot)) 46 | - Bump minimatch from 3.0.4 to 3.1.2 [#45](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/45) ([@dependabot](https://github.com/dependabot)) 47 | - Bump loader-utils from 1.4.0 to 1.4.1 [#44](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/44) ([@dependabot](https://github.com/dependabot)) 48 | - Bump moment from 2.29.2 to 2.29.4 [#43](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/43) ([@dependabot](https://github.com/dependabot)) 49 | - Bump terser from 5.7.0 to 5.14.2 [#42](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/42) ([@dependabot](https://github.com/dependabot)) 50 | - Bump shell-quote from 1.7.2 to 1.7.3 [#41](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/41) ([@dependabot](https://github.com/dependabot)) 51 | - Bump moment from 2.29.1 to 2.29.2 [#40](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/40) ([@dependabot](https://github.com/dependabot)) 52 | - Bump nanoid from 3.1.23 to 3.2.0 [#34](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/34) ([@dependabot](https://github.com/dependabot)) 53 | 54 | ### Contributors to this release 55 | 56 | ([GitHub contributors page for this release](https://github.com/jupyterlab-contrib/jupyterlab-link-share/graphs/contributors?from=2021-11-19&to=2022-12-03&type=c)) 57 | 58 | [@dependabot](https://github.com/search?q=repo%3Ajupyterlab-contrib%2Fjupyterlab-link-share+involves%3Adependabot+updated%3A2021-11-19..2022-12-03&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyterlab-contrib%2Fjupyterlab-link-share+involves%3Ajtpio+updated%3A2021-11-19..2022-12-03&type=Issues) 59 | 60 | ## 0.2.4 61 | 62 | ([Full Changelog](https://github.com/jupyterlab-contrib/jupyterlab-link-share/compare/v0.2.3...ffb6a15bbae346be5a491f0e36fd9811038bdd4b)) 63 | 64 | ### Enhancements made 65 | 66 | - Show correct URL when using retrolab [#27](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/27) ([@yuvipanda](https://github.com/yuvipanda)) 67 | 68 | ### Maintenance and upkeep improvements 69 | 70 | - Bump ansi-regex from 5.0.0 to 5.0.1 [#28](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/28) ([@dependabot](https://github.com/dependabot)) 71 | 72 | ### Contributors to this release 73 | 74 | ([GitHub contributors page for this release](https://github.com/jupyterlab-contrib/jupyterlab-link-share/graphs/contributors?from=2021-11-19&to=2021-11-19&type=c)) 75 | 76 | [@dependabot](https://github.com/search?q=repo%3Ajupyterlab-contrib%2Fjupyterlab-link-share+involves%3Adependabot+updated%3A2021-11-19..2021-11-19&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyterlab-contrib%2Fjupyterlab-link-share+involves%3Ajtpio+updated%3A2021-11-19..2021-11-19&type=Issues) | [@yuvipanda](https://github.com/search?q=repo%3Ajupyterlab-contrib%2Fjupyterlab-link-share+involves%3Ayuvipanda+updated%3A2021-11-19..2021-11-19&type=Issues) 77 | 78 | ## 0.2.3 79 | 80 | ([Full Changelog](https://github.com/jupyterlab-contrib/jupyterlab-link-share/compare/v0.2.2...1d6bfefc6c0cbfc45217381350dd84de4378fd6d)) 81 | 82 | ### Enhancements made 83 | 84 | - Add a rudimentary warning to the link share dialog [#24](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/24) ([@yuvipanda](https://github.com/yuvipanda)) 85 | 86 | ### Maintenance and upkeep improvements 87 | 88 | - Bump ansi-regex from 5.0.0 to 5.0.1 [#23](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/23) ([@dependabot](https://github.com/dependabot)) 89 | 90 | ### Contributors to this release 91 | 92 | ([GitHub contributors page for this release](https://github.com/jupyterlab-contrib/jupyterlab-link-share/graphs/contributors?from=2021-11-19&to=2021-11-19&type=c)) 93 | 94 | [@dependabot](https://github.com/search?q=repo%3Ajupyterlab-contrib%2Fjupyterlab-link-share+involves%3Adependabot+updated%3A2021-11-19..2021-11-19&type=Issues) | [@yuvipanda](https://github.com/search?q=repo%3Ajupyterlab-contrib%2Fjupyterlab-link-share+involves%3Ayuvipanda+updated%3A2021-11-19..2021-11-19&type=Issues) 95 | 96 | ## 0.2.2 97 | 98 | ([Full Changelog](https://github.com/jupyterlab-contrib/jupyterlab-link-share/compare/v0.2.1...526163e1029b265c52f05f37466e365c30a28a3b)) 99 | 100 | ### Bugs fixed 101 | 102 | - Use default user token when running under JupyterHub [#21](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/21) ([@yuvipanda](https://github.com/yuvipanda)) 103 | 104 | ### Contributors to this release 105 | 106 | ([GitHub contributors page for this release](https://github.com/jupyterlab-contrib/jupyterlab-link-share/graphs/contributors?from=2021-09-13&to=2021-11-19&type=c)) 107 | 108 | [@jtpio](https://github.com/search?q=repo%3Ajupyterlab-contrib%2Fjupyterlab-link-share+involves%3Ajtpio+updated%3A2021-09-13..2021-11-19&type=Issues) | [@yuvipanda](https://github.com/search?q=repo%3Ajupyterlab-contrib%2Fjupyterlab-link-share+involves%3Ayuvipanda+updated%3A2021-09-13..2021-11-19&type=Issues) 109 | 110 | ## 0.2.1 111 | 112 | ([Full Changelog](https://github.com/jupyterlab-contrib/jupyterlab-link-share/compare/0.2.0...a9ab3dde03285a7b8597ceb1e26412bcf01bebca)) 113 | 114 | ### Maintenance and upkeep improvements 115 | 116 | - Adopt Jupyter Packaging 0.10 and the releaser [#16](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/16) ([@jtpio](https://github.com/jtpio)) 117 | 118 | ### Other merged PRs 119 | 120 | - Bump tar from 6.1.5 to 6.1.11 [#15](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/15) ([@dependabot](https://github.com/dependabot)) 121 | - Bump url-parse from 1.5.1 to 1.5.3 [#14](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/14) ([@dependabot](https://github.com/dependabot)) 122 | - Bump path-parse from 1.0.6 to 1.0.7 [#13](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/13) ([@dependabot](https://github.com/dependabot)) 123 | - Bump tar from 6.1.0 to 6.1.5 [#12](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/12) ([@dependabot](https://github.com/dependabot)) 124 | - Bump normalize-url from 4.5.0 to 4.5.1 [#11](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/11) ([@dependabot](https://github.com/dependabot)) 125 | - Bump ws from 7.4.5 to 7.4.6 [#9](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/9) ([@dependabot](https://github.com/dependabot)) 126 | - Fix security vulnerabilities [#5](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/5) ([@fcollonval](https://github.com/fcollonval)) 127 | - Handle multiple servers [#4](https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/4) ([@jtpio](https://github.com/jtpio)) 128 | 129 | ### Contributors to this release 130 | 131 | ([GitHub contributors page for this release](https://github.com/jupyterlab-contrib/jupyterlab-link-share/graphs/contributors?from=2021-02-10&to=2021-09-13&type=c)) 132 | 133 | [@dependabot](https://github.com/search?q=repo%3Ajupyterlab-contrib%2Fjupyterlab-link-share+involves%3Adependabot+updated%3A2021-02-10..2021-09-13&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Ajupyterlab-contrib%2Fjupyterlab-link-share+involves%3Afcollonval+updated%3A2021-02-10..2021-09-13&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyterlab-contrib%2Fjupyterlab-link-share+involves%3Ajtpio+updated%3A2021-02-10..2021-09-13&type=Issues) 134 | 135 | ## 0.2.0 136 | 137 | ### Changes 138 | 139 | - Handle multiple servers: https://github.com/jupyterlab-contrib/jupyterlab-link-share/pull/4 140 | --------------------------------------------------------------------------------