Giter Club home page Giter Club logo

paranext-core's Introduction

paranext-core

Electron client, extension host, and C# library for Paranext

Build Status CodeQL Github Tag

Users

This software is not yet ready for users. We'll update here with where you can install it when its ready.

Linux Users

To use .AppImage files in Linux, install FUSE (you only need to do this once), for example, on Ubuntu (>= 22.04):

sudo apt install libfuse2

Then simply execute/run the .AppImage file, which you can download from Releases.

Developer Install

Set up pre-requisites for building:

Linux Development Pre-requisites

Add the system libraries needed for Electron, Build Instructions (Linux).

All Platforms Development Pre-requisites

Install Node.js version >=18.0.0 (18.0.0 or greater is required for using fetch). We recommend using Volta.

Install dotnet .NET 8 SDK from here.

To check if dotnet is installed run (ensure you have a v8 SDK):

dotnet --version
dotnet --list-sdks

Cloning and installing dependencies (all platforms)

Clone the repo and install dependencies:

git clone https://github.com/paranext/paranext-core.git
cd paranext-core
npm install

To build the declaration type file papi.d.ts for extensions to use, run the following:

npm run build:types

Starting Development

Start the app in the dev environment:

npm start

After you run npm start (or, in VSCode, launch Debug Paranext Core), you can edit the code, and the relevant processes will hot reload.

Developing Extensions

Paranext Core extensions are found in the extensions folder. Please follow the instructions in extensions/README.md to develop extensions.

GitHub Pages

Platform.Bible API Documentation

  • Explore the declarations of types available on the PAPI.

Platform.Bible React Components and Hooks Documentation

  • Check out the React components and hooks available to use.

Platform.Bible Utilities Documentation

  • Check out the utility functions, types, and classes available to use.

JavaScript Tool Manager

You can use Volta with this repo to use the right version of tools such as node and npm.

If you don't use Volta just look at the volta property in package.json to see the right tool versions to install in your preferred way.

Packaging for Production

To package apps for the local platform:

npm run package

Publishing

  1. Create a branch of the form release/*, e.g. release/v1.2.3, or release/v1.2.3-rc1.
  2. Update the version in your project's release/app/package.json, e.g.:
    cd ./release/app
    npm version 1.2.3
  3. Create a new draft GitHub Release, ensure the following are included:
    • a Tag version, e.g. v1.2.3, choose Create new tag on publish.
    • set the Target to the release branch.
    • a copy of the change log. Click Generate release notes as a starting point.
    • Click Save draft.
  4. Update CHANGELOG.md with changes in this release from the GitHub draft Release.
  5. Commit these changes to your release branch and push the commit to GitHub.
  6. Once the GitHub build Action has finished, it will add build artifact files to the draft release. Remove the .blockmap files and leave the .yml files and the installers and executable, e.g. .exe on Windows.
  7. Publish the release on GitHub.
  8. Merge the release branch back into main with a merge commit.

Testing

The following tests run automatically on each GitHub PR (see test.yml).

To run C# unit tests:

cd c-sharp-tests
dotnet test

To run C# unit tests watching for file changes:

cd c-sharp-tests
dotnet watch test

To run all TS unit tests:

npm test

To run an individual TS unit test watching for file changes:

npm test -- <path/to/test-file.test.ts> --watch

You can also use the recommended VS Code extensions to run tests there.

Storybook

To run Storybook locally:

npm run storybook

To build Storybook:

npm run storybook:build

To run Storybook as a web app, after it was built successfully:

npx http-server ./storybook-static

Windows Development with WSL2

On Windows, you can install WSL (Windows Subsystem for Linux) so you can test cross-platform compatibility on Linux (as well as Windows). You'll need to use a Linux distribution with WSL2 (rather than WSL1) so the X-Server windows can be opened for Electron.

  1. Here is how to install Linux on Windows with WSL.
  2. You'll want to follow that by setting up to use VS Code, Git and NodeJS with WSL. See the various tutorials.
  3. In the WSL distribution, add system libraries needed for Electron, see Linux Development Pre-requisites above.
  4. In the WSL distribution, clone the repo as described above under Developer Install.

You'll be running a copy of the repo in both Windows and WSL so make sure they are both up-to-date.

You can use VS Code from your host to access code in your WSL repo clone using the Microsoft Remote Development VS Code extension.

VS Code Extension Options

Extensions highly recommended for this repo are already displayed in VS Code through the Extensions Recommendations settings. These are optional extensions that our developers enjoy using:

Formatting and Linting

Formatting happens automatically when you commit. If you use VS Code with this repo's recommended extensions it will format when you save.

To check TypeScript for readability, maintainability, and functionality errors, and to check a few other files for proper formatting, run the following from the repo root (or just use VS Code with this repo's recommended extensions)

npm run prettier
npm run lint

To check C# for readability, maintainability, and functionality errors, run the following from the repo root (or just use VS Code with this repo's recommended extensions)

cd c-sharp
dotnet tool restore
dotnet csharpier .

Documentation in papi.d.ts

VSCode renders JSDoc comments in the UI to make it easier for developers to use functions and properties as intended. However, those comments do not always propagate from modules to the d.ts type definition file when those modules are re-exported. To help with this problem in papi.d.ts that we export for extensions to reference, we added some custom functionality.

If you want comments to be copied from one location in papi.d.ts to another, do the following:

  • In the JSDoc comments that you want copied elsewhere, add "JSDOC SOURCE myServiceName" (must have a blank line after) in the JSDoc comments like this:
/**
 * JSDOC SOURCE myService
 *
 * myService is amazing. Here are more details about it.
 * ...
 */
const myService = {
  ...
}
  • In the location where you want the docs copied, add "JSDOC DESTINATION myServiceName" like this:
const papi = {
  ...
  /** JSDOC DESTINATION myService */
  myService,

Thanks

Some important decisions in this project were inspired by the work done in Visual Studio Code. Thanks VS Code developers for some great ideas!

License

MIT © SIL International

paranext-core's People

Contributors

tjcouch-sil avatar rolfheij-sil avatar irahopkinson avatar tombogle avatar lyonsil avatar katherinejensen00 avatar foolrunning avatar jolierabideau avatar dependabot[bot] avatar sebastian-ubs avatar rolfheij avatar merchako avatar dewert99 avatar theguidingstar avatar lingting avatar

Stargazers

Prabhu Beelagi avatar  avatar  avatar  avatar  avatar mooon avatar  avatar  avatar Chris Vickio avatar TJ Couch avatar  avatar James Cuénod avatar Mike B. avatar

Watchers

 avatar Michael Marshall avatar  avatar  avatar  avatar

paranext-core's Issues

Make extension logger contextual

Make the extension logger better by either doing something similar to VSCode's Telemetry or make some system that prefixes logs with the extension name or something along those lines.

Depends on #50

Use different tsconfig path aliases to split code between processes

We should use different tsconfig files with different path aliases on @process or something to allow us to split synchronous code between different modules. This would help to clean up WebSockets and allow us to get a cryptographically secure nonce more easily.

This was previously an issue about NormalModuleReplacementPlugin, but TJ changed it when he made a demo branch for similar work #190 main...fd04d8a most of the original info is the same, so it is preserved below. It would also be useful to add the tslint rule mentioned in 190 https://typescript-eslint.io/rules/no-restricted-imports

We should also investigate if you can exclude the process folder in the other path aliases like @extension-host to avoid duplicity and confusion.

--- Original ---

Currently, we are using the IgnorePlugin to split code between processes. This leads to messy dynamic imports in Factory files like NetworkConnectorFactory.ts. We should instead use NormalModuleReplacementPlugin, which will clean up imports and make it possible to split out code more easily. Also need to stop using conditional require in papi.ts.

Hopefully, this change will allow us to throw errors if webpack discovers that a process is using a file outside of its appropriate reach instead of silently ignoring the import like we are doing now. Also investigate if eslint allows you to show importing some regex as an error so you can show errors for importing in the wrong folders at write-time.

We also need to stop using globalThis for variables we need that differ depending on the process. Let's use NormalModuleReplacementPlugin instead. We can make an abstract class in the shared folder that gets extended in each process that needs it with the appropriate implementations and overrides to tweak the object as needed, then create an instance of that class and export it. Like in NetworkService for example.

Maybe we can make aliases @process for this specific process type and @processGroup for shared code between the process types like node and client. However, that does not solve the problem with extension host getting code from both node and client folders.

Also probably should make a small replacement to allow using crypto in node process and in browser without having to require crypto on the node side. See Util.ts newNonce

Use assets in WebViews

Provide a way for WebViews to access assets like javascript files, images, and such. They may need a function that gives them contextually appropriate URIs. VSCode example

Depends on #44, #50

Add Disposable class/interface

Need to decide on if we will continue supporting UnsubscriberAsync or if we just want to give up on that complexity and just let people unsubscribe only after awaiting promises.

After that, need to add a Disposable class and interface so that we can dispose of things automatically through the PAPI instead of having everything keep track of a bunch of unsubscribers manually.

C#: Pair requests to their respective responses

When doing some testing with the C# client after implementing events, an exception occurred that crashed the process:

13:34:33.952 > [dotnet data provider] stderr: System.ArgumentException: Unexpected message type: event
   at Paranext.DataProvider.Data.MessageConverter.Read(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options) in C:\Users\tj_co\source\repos\paranext-core\c-sharp\Data\MessageConverter.cs:line 34
   at System.Text.Json.Serialization.Converters.CastingConverter`2.Read(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 utf8Json, JsonTypeInfo jsonTypeInfo, Nullable`1 actualByteCount)
   at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 json, JsonTypeInfo jsonTypeInfo)
   at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options)
   at Paranext.DataProvider.Web.PapiClient.ReceiveMessage[TReturn]() in C:\Users\tj_co\source\repos\paranext-core\c-sharp\Web\PapiClient.cs:line 220
   at Paranext.DataProvider.Web.PapiClient.RegisterRequest(Enum`1 requestToHandle, Func`2 doStuff) in C:\Users\tj_co\source\repos\paranext-core\c-sharp\Web\PapiClient.cs:line 104
   at Paranext.DataProvider.Program.Main() in C:\Users\tj_co\source\repos\paranext-core\c-sharp\Program.cs:line 26
   at Paranext.DataProvider.Program.<Main>()

I believe the C# client is expecting a response message from the server immediately following its request message. When something that isn't a response comes in, bad things happen (in this case, an event came in which the C# client doesn't currently know how to handle. So the exception doesn't look like the problem I'm describing. But handling events in C# is in #28). However, I don't think it will be guaranteed in the future that a response will occur immediately after a request. As such, I propose we associate a request Id to a Task that request id represents so that, when a response comes in for that request Id, the Task can be resolved appropriately. Otherwise, we can always handle messages appropriately

Create some basic components (MUI)

  • Get MUI
  • Create a few basic components that wrap MUI components and use our own props (the MUI component props and such should not be exposed to the papi. It should be an implementation detail
    • Button
    • Input field
    • Switch (on/off)
    • Dropdown select (can't tell if there is one in mui. Maybe get from here if not https://react-select.com/home )
    • Slider
    • Others you think will be useful

Depends on #39

Fix storybook production build?

Our production version of storybook (static page that shows the components and their abilities) isn't working. If we ever need to make it work (it may be a convenient way to document and demonstrate to extension developers what components they can use), we need to fix the production webpack configuration in .storybook/main.ts.

The cost to fix the storybook publication build was too expensive so we decided to create a separate repo that would have all of the components and run storybook. This turned into a research task to understand what options are available for Figma and Storybook integration. Below is the link to the research results:

Figma and Storybook Integration Research

To test the production storybook, follow the steps here: https://storybook.js.org/docs/react/sharing/publish-storybook

Depends on #39

Create contextually appropriate file system access on papi

Need to make a way for extensions to access papi in a controlled way. They need to be force-restricted to accessing only their own project folder and their own user folder and such. Maybe we can make a proxy for each extension that restricts certain functionality based on "attributes" attached to the properties of the services on the papi or something. We also need to consider how to do this for web views as well.

Make a note to extension developers that they can use this in place of fs.

It would be wise to investigate VSCode's context object they pass into the activate function and get an idea how that might be helpful to understand for our design.

Depends on #30, #56

C#: Send requests from C#

As an extension developer, I want to be able to interact with APIs exposed on the C# side that interact with the platform in meaningful ways like getting the current scripture reference.


We need to make general requests from the C# side. For example, we will need to tell the PAPI which events to send us from the network assuming we end up doing that in #22 as planned (the actual work of registering for events would be in #58, not here). Or maybe we need to ask the frontend to give us the current scripture reference. Or something along these lines. Assuming we believe this will be a need, let's implement this.

Looks like we can adapt the code around here to make a new Request method.

Distributed repository strategy

As an architect I want to have a rough plan of how Paranext can be subdivided into different repositories.

Develop a plan for how to subdivide Paranext into distributed but related github repos. Setup any additional repos that we know we need now.

Create DockableUI Framework

https://www.npmjs.com/package/rc-dock
https://github.com/tjcouch-sil/paranext-poc/tree/main/react-electron-poc

  • Choose a framework that is extensible and allows theming
  • Ability to add functionality to the docking framework

Determine how we can support the following:

  • Tabs
  • Draggable panels
  • Drop panels by location of mouse similar to PT9
  • Autohide
  • Contextual area dropzones
  • Buttons in tabs
  • Buttons in tab groups
  • Floating Windows
  • Ability to move dockable floating window to a separate window

Any features that we would need to write a lot of custom code for we should create issues to handle in the future.

Load and run extension file (hello world)

Load up extension file in the extension host and let it run papi stuff

Ideas:

  • Need file service
  • In debug mode, watch unzipped extension files and reload as appropriate
  • Webpack/babel

Create generated documentation of our api

As an extension developer, I want to be able to go somewhere not in the code to find all the features of the API so I can easily navigate around and get a detailed view of what I can do.

We need to find a way to generate an external document describing PAPI and possibly hook it up to our CICD process.

Looks like TypeDoc does what we want:
https://github.com/TypeStrong/typedoc
Examples:

There are others in case we want to try multiple.

Scaffold Core - Combine Electron, React, Edge, C#

As a developer, I want to develop on a base of code projects including the relevant technologies for the software so I can develop the software.

  • Combine ERB and Edge
  • Add C# solution that integrates well with the Node stuff

Give credit to VSCode

We should think of a nice way to credit VSCode for all the inspiration and wisdom they have given us. Maybe add a shoutout to them in our readme.

Add Uri class

  • Need to make Uri into a class instead of just a string. Need to make it serializable and deserializable across the PAPI. Maybe adjust JSON.stringify and JSON.parse functions to allow this
  • That way, we can change the util functions for Uris into static functions on the Uri class
  • Also probably should change the name for the %appdata% folder from 'app' to 'user' or something to be less confusing

Create internet access service and shim internet access out

Create an internet access service on the papi, and shim out internet access so extension developers can't access the internet outside our avenues. Probably pretty much just perfectly mirror the fetch api (maybe even just pass the args straight to it).

Depends on #30

Closely related to #75, but not quite dependent on it.

Integrate resource viewer code

As an architect I need to provide an example extension that does something useful.

Develop an extension that appears in the Paranext "Hello World" example.

Consider using Chris Klapp's Scripture viewer code to display Scripture text in a read only view.

Depends on #44, #59

Create Table component

Scaffold Core - Combine Electron, React, C# websocket

As a developer, I want to develop on a base of code projects including the relevant technologies for the software so I can develop the software.

  • Communicate between the Electron Main process and the renderer process through WebSocket
  • Spawn extension process manager from Electron main process
  • Create C# project as a websocket client
  • Communicate between extension process manager and C# project

C#: Send and receive events

We probably need the ability to listen for and to send events on the PAPI from C#. (If we want to wait until we actually need it, that's fine.)

Depends on #22

Fix startup race conditions

The main process's web server, renderer process client, extension host client, and C# client all start at different times and don't wait on each other well. We need to fix this problem. Probably this task should mainly be about retrying WebSocket connections when they fail until they succeed. Hopefully, the activation event issue #51 may solve problems relating to actual commands not sending properly and such, but maybe this task can solve remaining issues if there are any after that point.

Add multi-format icon files

When you run npm run package, you get an installer at paranext-core\release\build that has a generic installer icon. Let's fix it to use our icon.

EDIT: Changed to just uploading icon files. Actual installer updating will happen in #14

Fix flickering ellipsis (...) button on tab groups

Sometimes, when you resize tab groups just the right amount, the ... button that shows up when there are too many tabs to display flickers on and off the screen. This is probably some small bug in rc-dock that has to do with not testing the widths correctly according to how we're doing things. We should use the patch package on rc-dock.

Register to listen for events

Currently, events on the WebSocket go around to all clients whether or not they need to listen to those WebSocket messages.

Make events more efficient by adding event registration. Each process that wants events needs to tell main that it wants those events.

We can do this by adding a routeEvent function in NetworkService similar to its routeRequest that tells the ServerNetworkConnector which clients to send event messages to. Then we can also add a new serverRequestHandlers entry in NetworkService that registers and unregisters processes from listening to events.

Split out from and depends on #22

Create DataProvider API

Create a way to provide data from the PAPI.

  • Data providers pull data from some data source (in whatever data shape) and create their own data in their extension
  • PAPI-provided registration and unregistration functions and hooks to ingest the data
  • Ingestors need to be able to specify selectors that limit the amount of data being provided to them
  • Need setters to change the data
  • PAPI should provide events that update the data on the frontend when it updates on the backend

Depends on #45

Add a nice command-line argument system with a few args

We need to be able to add a few command-line arguments when running Paranext:

  • Log level
  • Debug mode
  • Don't run the extension host (dev environment only...?)
  • Don't run the C# client (dev environment only...?)
  • Don't run the renderer (headless)

Change launch.json to run C# from npm scripts

Change launch.json configuration for .NET Core Launch so it runs an npm script like npm run start:data. That way, people can run the code with or without VSCode all from npm similar to how the Electron: Main configuration just runs npm run start. Make sure VSCode debugging is preserved.

End websocket connections and main process

We need a way to close Paranext well, which closes all websocket connections and processes cleanly.

A couple of Node closing libraries:
https://www.npmjs.com/package/node-cleanup - very old but seemingly quite flexible/powerful. Seems like it may run async closing code on process.exit(), but it seems unclear
https://www.npmjs.com/package/exit-hook - newer, maintained library that has an asynchronous closing code api but does not successfully await async closing code when using process.exit(). It has a gracefulExit() function to use instead

Related discussions with details about Node processes, WebSocket connections, and ideas for closing them well:
#36 (comment)
#36 (comment)

See #115

Enable papi operations to wait for extension activation

We need to be able to wait on extensions to load when we run commands or do other dependent things on those extensions. Design from #180:

Every request polls a few times to see if the request becomes available at some point. Easy dependency resolution (unfortunately, this slows down failures on unavailable requests, but whatever. It also does not solve circular request dependencies, but who cares)

Depends on #30, #22

Recommend Projects

  • React photo React

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

  • Vue.js photo Vue.js

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

  • Typescript photo Typescript

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

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

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

  • web

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

  • server

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

  • Machine learning

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

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

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

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.