Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrading from 0.8.20 to 0.9.6 #427

Open
rmmason opened this issue Feb 17, 2025 · 2 comments
Open

Upgrading from 0.8.20 to 0.9.6 #427

rmmason opened this issue Feb 17, 2025 · 2 comments

Comments

@rmmason
Copy link

rmmason commented Feb 17, 2025

We had a project that was performing interop from .NET to JS using version 0.8.20 but were getting a couple of intermittent errors when trying to run outside of our development environment.

The two errors we are getting are:

  1. Already initialized. The initialize function can be used only once.
  2. The JS reference cannot be accessed from the current thread.

We tried to resolve this by updating to the latest nuget packages but it looks like there have been some breaking changes and the docs are no longer up-to-date.

We are using DI and used to store NodeJsPlatform as a singleton and register NodeJsEnvironment as scoped and pass this into another scoped service that executes some JavaScript to render a PDF report.

We have now replaced this with NodeEmbeddingPlatform as a singleton which is passed into the scoped report generation service which then creates a NodeEmbeddingThreadRuntime via platform.CreateThreadRuntime(); and executes await threadRuntime.Run(async () =>...

With the latest code when trying to create the NodeEmbeddingPlatform we get the following error:

"System.EntryPointNotFoundException : Unable to find an entry point named 'node_embedding_platform_create' in DLL."

Could someone please give us some hints as to how to get the latest version working and any pointers as to what our initial issues may be related to?

@rmmason
Copy link
Author

rmmason commented Feb 18, 2025

As an update. We managed to get version 0.9.6 running using the libnode libraries from here: https://github.com/alethic/Microsoft.JavaScript.LibNode/actions/runs/13220509513

But during testing the code inside the await threadRuntime.RunAsync(async () => ... ) ran to conclusion but the Task never returns.

I have gone back to the latest nuget package before NodeJsPlatform was removed (0.9.3) a simple test works but when we run within a Azure Functions problem the first run works but then we get a "JS reference cannot be accessed from the current thread" error.

I would really appreciate some help debugging the issues we are having.

@rmmason
Copy link
Author

rmmason commented Feb 18, 2025

I'm pretty sure we are doing something wrong in terms of the interop with a stream we are intending to pass to the JavaScript. Our intention is to create a MemoryStream and have the JavaScript fill the stream with data. The .NET hosting code looks like this:

What we want to do is pass a memory stream to the JS and have the JS fill the stream.

Our code at the .NET end currently looks like this:

public async Task RenderReportToStream(Stream stream, string reportJson)
{
    using NodejsEnvironment nodejsEnvironment = _nodejsPlatform.CreateEnvironment(_appBaseDir);

    await nodejsEnvironment.RunAsync(async () =>
    {
        if (Debugger.IsAttached)
        {
            var inspectorIUrl = nodejsEnvironment.StartInspector();
            Debug.WriteLine($"Inspector Attached at URL: {inspectorIUrl.AbsolutePath}");
        }

        var importJsValue = nodejsEnvironment.Import("@anon/pdf-report-renderer", "ReportRenderer", true);

        if (!importJsValue.IsPromise())
        {
            throw new Exception("Expected import JSValue to return a promise as we are importing as ES Module.");
        }

        var importJsValuePromise = importJsValue.As<JSPromise>() ?? throw new Exception("Expected import to be castable to promise but got <null>.");

        var importedReportRenderer = await importJsValuePromise.AsTask();

        var jsSampleReportJson = JSValue.CreateStringUtf16(reportJson);

        using MemoryStream ms = new MemoryStream();
        using Stream synchStream = NodeStream.Synchronized(ms);

        var marshaller = new JSMarshaller { AutoCamelCase = true };

        var jsStream = marshaller.ToJS(synchStream);

        JSValue jsReportRendererObj = importedReportRenderer.CallAsConstructor();

        var jsResult = jsReportRendererObj.CallMethod("renderReport", jsSampleReportJson, jsStream);

        if (!jsResult.IsPromise())
        {
            throw new Exception("Expected result from report renderer to be a promise.");
        }

        var jsPromise = jsResult.As<JSPromise>() ?? throw new Exception("Expected result from report renderer to be castable to promise but received <null>.");

        await jsPromise.AsTask();

        ms.Position = 0;
        ms.CopyTo(stream);

        if (Debugger.IsAttached)
        {
            nodejsEnvironment.StopInspector();
        }
    });
}

And the JavaScript being called looks like this:

import {
    ReportDefinitionBuilder,
    ReportDataModel
} from '@anon/pdf-report-viewer';
import { Duplex } from 'stream';

export class ReportRenderer {
    constructor() {
    }

    public async renderReport(jsonReportData: string, stream: Duplex): Promise<void> {
        let reportDataModel = JSON.parse(jsonReportData) as ReportDataModel;
        let reportDefinition = ReportDefinitionBuilder(reportDataModel);
        await reportDefinition.render();
        await reportDefinition.getArrayBuffer()
            .then(async (ab) => {
                stream.write(Buffer.from(ab), "binary");
            });
        return;
    }
}

I think we are probably going wrong when we create an instance of JSMarshaller and use this to wrap the MemoryStream. Could anyone please give us a hint at how this should be done?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant