Skip to content

Commit

Permalink
sea: support sea.getRawAsset()
Browse files Browse the repository at this point in the history
This patch adds support for `sea.getRawAsset()` which is
similar to `sea.getAsset()` but returns the raw asset
in an array buffer without copying. Users should avoid
writing to the returned array buffer. If the injected
section is not marked as writable or not aligned,
writing to the raw asset is likely to result in a crash.

PR-URL: nodejs#50960
Refs: nodejs/single-executable#68
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
  • Loading branch information
joyeecheung authored and marco-ippolito committed Feb 19, 2024
1 parent 4ee848e commit a21093d
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 0 deletions.
23 changes: 23 additions & 0 deletions doc/api/single-executable-applications.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ const image = getAsset('a.jpg');
const text = getAsset('b.txt', 'utf8');
// Returns a Blob containing the asset.
const blob = getAssetAsBlob('a.jpg');
// Returns an ArrayBuffer containing the raw asset without copying.
const raw = getRawAsset('a.jpg');
```

See documentation of the [`sea.getAsset()`][] and [`sea.getAssetAsBlob()`][]
Expand Down Expand Up @@ -316,6 +318,27 @@ An error is thrown when no matching asset can be found.
* `type` {string} An optional mime type for the blob.
* Returns: {Blob}

### `sea.getRawAsset(key)`

<!-- YAML
added: REPLACEME
-->

This method can be used to retrieve the assets configured to be bundled into the
single-executable application at build time.
An error is thrown when no matching asset can be found.

Unlike `sea.getRawAsset()` or `sea.getAssetAsBlob()`, this method does not
return a copy. Instead, it returns the raw asset bundled inside the executable.

For now, users should avoid writing to the returned array buffer. If the
injected section is not marked as writable or not aligned properly,
writes to the returned array buffer is likely to result in a crash.

* `key` {string} the key for the asset in the dictionary specified by the
`assets` field in the single-executable application configuration.
* Returns: {string|ArrayBuffer}

### `require(id)` in the injected main script is not file based

`require()` in the injected main script is not the same as the [`require()`][]
Expand Down
1 change: 1 addition & 0 deletions lib/sea.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,6 @@ function getAssetAsBlob(key, options) {
module.exports = {
isSea,
getAsset,
getRawAsset,
getAssetAsBlob,
};
31 changes: 31 additions & 0 deletions test/fixtures/sea/get-asset-raw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use strict';

const { isSea, getAsset, getRawAsset } = require('node:sea');
const { readFileSync } = require('fs');
const assert = require('assert');

assert(isSea());

{
assert.throws(() => getRawAsset('nonexistent'), {
code: 'ERR_SINGLE_EXECUTABLE_APPLICATION_ASSET_NOT_FOUND'
});
assert.throws(() => getRawAsset(null), {
code: 'ERR_INVALID_ARG_TYPE'
});
assert.throws(() => getRawAsset(1), {
code: 'ERR_INVALID_ARG_TYPE'
});
}

{
// Check that the asset embedded is the same as the original.
const assetOnDisk = readFileSync(process.env.__TEST_PERSON_JPG);
const assetCopy = getAsset('person.jpg')
const assetCopyBuffer = Buffer.from(assetCopy);
assert.deepStrictEqual(assetCopyBuffer, assetOnDisk);

// Check that the copied asset is the same as the raw one.
const rawAsset = getRawAsset('person.jpg');
assert.deepStrictEqual(rawAsset, assetCopy);
}
2 changes: 2 additions & 0 deletions test/fixtures/sea/get-asset.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ const binaryAssetOnDisk = readFileSync(process.env.__TEST_PERSON_JPG);
{
const actualAsset = getAsset('utf8_test_text.txt', 'utf8')
assert.strictEqual(actualAsset, textAssetOnDisk);
// Log it out so that the test could compare it and see if
// it's encoded/decoded correctly in the SEA.
console.log(actualAsset);
}

Expand Down
1 change: 1 addition & 0 deletions test/sequential/sequential.status
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ test-performance-eventloopdelay: PASS, FLAKY

[$system==linux && $arch==ppc64]
# https://github.com/nodejs/node/issues/50740
test-single-executable-application-assets-raw: PASS, FLAKY
test-single-executable-application-assets: PASS, FLAKY
test-single-executable-application-disable-experimental-sea-warning: PASS, FLAKY
test-single-executable-application-empty: PASS, FLAKY
Expand Down
73 changes: 73 additions & 0 deletions test/sequential/test-single-executable-application-assets-raw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
'use strict';

const common = require('../common');

const {
injectAndCodeSign,
skipIfSingleExecutableIsNotSupported,
} = require('../common/sea');

skipIfSingleExecutableIsNotSupported();

// This tests the snapshot support in single executable applications.
const tmpdir = require('../common/tmpdir');

const { copyFileSync, writeFileSync, existsSync } = require('fs');
const {
spawnSyncAndExitWithoutError,
} = require('../common/child_process');
const assert = require('assert');
const fixtures = require('../common/fixtures');

tmpdir.refresh();
if (!tmpdir.hasEnoughSpace(120 * 1024 * 1024)) {
common.skip('Not enough disk space');
}

const configFile = tmpdir.resolve('sea-config.json');
const seaPrepBlob = tmpdir.resolve('sea-prep.blob');
const outputFile = tmpdir.resolve(process.platform === 'win32' ? 'sea.exe' : 'sea');

{
tmpdir.refresh();
copyFileSync(fixtures.path('sea', 'get-asset-raw.js'), tmpdir.resolve('sea.js'));
copyFileSync(fixtures.path('person.jpg'), tmpdir.resolve('person.jpg'));
writeFileSync(configFile, `
{
"main": "sea.js",
"output": "sea-prep.blob",
"assets": {
"person.jpg": "person.jpg"
}
}
`, 'utf8');

spawnSyncAndExitWithoutError(
process.execPath,
['--experimental-sea-config', 'sea-config.json'],
{
env: {
NODE_DEBUG_NATIVE: 'SEA',
...process.env,
},
cwd: tmpdir.path
},
{});

assert(existsSync(seaPrepBlob));

copyFileSync(process.execPath, outputFile);
injectAndCodeSign(outputFile, seaPrepBlob);

spawnSyncAndExitWithoutError(
outputFile,
{
env: {
...process.env,
NODE_DEBUG_NATIVE: 'SEA',
__TEST_PERSON_JPG: fixtures.path('person.jpg'),
}
},
{ }
);
}

0 comments on commit a21093d

Please sign in to comment.