🚀 Big News: Socket Acquires Coana to Bring Reachability Analysis to Every Appsec Team.Learn more
Socket
DemoInstallSign in
Socket

aperture

Package Overview
Dependencies
Maintainers
3
Versions
23
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

aperture - npm Package Compare versions

Comparing version

to
6.0.0

index.d.ts

153

index.js
'use strict';
const os = require('os');
const util = require('util');
const {debuglog} = require('util');
const path = require('path');

@@ -10,4 +10,6 @@ const execa = require('execa');

const electronUtil = require('electron-util/node');
const delay = require('delay');
const debuglog = util.debuglog('aperture');
const log = debuglog('aperture');
const getRandomId = () => Math.random().toString(36).slice(2, 15);

@@ -18,4 +20,7 @@ // Workaround for https://github.com/electron/electron/issues/9459

const supportsHevcHardwareEncoding = (() => {
if (!macosVersion.isGreaterThanOrEqualTo('10.13')) {
return false;
const cpuModel = os.cpus()[0].model;
// All Apple silicon Macs support HEVC hardware encoding.
if (cpuModel.startsWith('Apple ')) { // Source string example: `'Apple M1'`
return true;
}

@@ -25,6 +30,9 @@

// More info: https://www.intel.com/content/www/us/en/processors/processor-numbers.html
const result = /Intel.*Core.*i(?:7|5)-(\d)/.exec(os.cpus()[0].model);
// Example strings:
// - `Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz`
// - `Intel(R) Core(TM) i7-4850HQ CPU @ 2.30GHz`
const result = /Intel.*Core.*i\d+-(\d)/.exec(cpuModel);
// Intel Core generation 6 or higher supports HEVC hardware encoding
return result && Number(result[1]) >= 6;
return result && Number.parseInt(result[1], 10) >= 6;
})();

@@ -34,3 +42,3 @@

constructor() {
macosVersion.assertGreaterThanOrEqualTo('10.12');
macosVersion.assertGreaterThanOrEqualTo('10.13');
}

@@ -47,2 +55,4 @@

} = {}) {
this.processId = getRandomId();
return new Promise((resolve, reject) => {

@@ -60,13 +70,15 @@ if (this.recorder !== undefined) {

if (typeof cropArea === 'object') {
if (typeof cropArea.x !== 'number' ||
typeof cropArea.y !== 'number' ||
typeof cropArea.width !== 'number' ||
typeof cropArea.height !== 'number') {
reject(new Error('Invalid `cropArea` option object'));
return;
}
if (typeof cropArea === 'object' &&
(
typeof cropArea.x !== 'number' ||
typeof cropArea.y !== 'number' ||
typeof cropArea.width !== 'number' ||
typeof cropArea.height !== 'number'
)
) {
reject(new Error('Invalid `cropArea` option object'));
return;
}
const recorderOpts = {
const recorderOptions = {
destination: fileUrl(this.tmpPath),

@@ -81,3 +93,3 @@ framesPerSecond: fps,

if (cropArea) {
recorderOpts.cropRect = [
recorderOptions.cropRect = [
[cropArea.x, cropArea.y],

@@ -104,7 +116,19 @@ [cropArea.width, cropArea.height]

recorderOpts.videoCodec = codecMap.get(videoCodec);
recorderOptions.videoCodec = codecMap.get(videoCodec);
}
this.recorder = execa(BIN, [JSON.stringify(recorderOpts)]);
this.recorder = execa(
BIN, [
'record',
'--process-id',
this.processId,
JSON.stringify(recorderOptions)
]
);
this.isFileReady = (async () => {
await this.waitForEvent('onFileReady');
return this.tmpPath;
})();
const timeout = setTimeout(() => {

@@ -116,8 +140,8 @@ // `.stopRecording()` was called already

const err = new Error('Could not start recording within 5 seconds');
err.code = 'RECORDER_TIMEOUT';
const error = new Error('Could not start recording within 5 seconds');
error.code = 'RECORDER_TIMEOUT';
this.recorder.kill();
delete this.recorder;
reject(err);
}, 10000);
reject(error);
}, 5000);

@@ -131,22 +155,83 @@ this.recorder.catch(error => {

this.recorder.stdout.setEncoding('utf8');
this.recorder.stdout.on('data', data => {
debuglog(data);
this.recorder.stdout.on('data', log);
if (data.trim() === 'R') {
// `R` is printed by Swift when the recording **actually** starts
(async () => {
try {
await this.waitForEvent('onStart');
clearTimeout(timeout);
resolve(this.tmpPath);
setTimeout(resolve, 1000);
} catch (error) {
reject(error);
}
});
})();
});
}
async stopRecording() {
async waitForEvent(name, parse) {
const {stdout} = await execa(
BIN, [
'events',
'listen',
'--process-id',
this.processId,
'--exit',
name
]
);
if (parse) {
return parse(stdout.trim());
}
}
async sendEvent(name, parse) {
const {stdout} = await execa(
BIN, [
'events',
'send',
'--process-id',
this.processId,
name
]
);
if (parse) {
return parse(stdout.trim());
}
}
throwIfNotStarted() {
if (this.recorder === undefined) {
throw new Error('Call `.startRecording()` first');
}
}
async pause() {
this.throwIfNotStarted();
await this.sendEvent('pause');
}
async resume() {
this.throwIfNotStarted();
await this.sendEvent('resume');
// It takes about 1s after the promise resolves for the recording to actually start
await delay(1000);
}
async isPaused() {
this.throwIfNotStarted();
return this.sendEvent('isPaused', value => value === 'true');
}
async stopRecording() {
this.throwIfNotStarted();
this.recorder.kill();
await this.recorder;
delete this.recorder;
delete this.isFileReady;

@@ -160,7 +245,7 @@ return this.tmpPath;

module.exports.screens = async () => {
const stderr = await execa.stderr(BIN, ['list-screens']);
const {stderr} = await execa(BIN, ['list', 'screens']);
try {
return JSON.parse(stderr);
} catch (_) {
} catch {
return stderr;

@@ -171,7 +256,7 @@ }

module.exports.audioDevices = async () => {
const stderr = await execa.stderr(BIN, ['list-audio-devices']);
const {stderr} = await execa(BIN, ['list', 'audio-devices']);
try {
return JSON.parse(stderr);
} catch (_) {
} catch {
return stderr;

@@ -178,0 +263,0 @@ }

{
"name": "aperture",
"version": "5.2.0",
"version": "6.0.0",
"description": "Record the screen on macOS",

@@ -8,7 +8,7 @@ "license": "MIT",

"engines": {
"node": ">=8"
"node": ">=10"
},
"scripts": {
"test": "xo && ava",
"build": "swift build --configuration=release --static-swift-stdlib -Xswiftc '-target' -Xswiftc 'x86_64-apple-macosx10.12' && mv .build/release/aperture .",
"test": "xo && ava && tsd",
"build": "swift build --configuration=release && mv .build/release/aperture .",
"prepublish": "npm run build"

@@ -18,17 +18,19 @@ },

"index.js",
"aperture"
"aperture",
"index.d.ts"
],
"dependencies": {
"electron-util": "^0.11.0",
"execa": "^1.0.0",
"file-url": "^2.0.2",
"macos-version": "^5.0.0",
"tempy": "^0.2.1"
"electron-util": "^0.14.2",
"execa": "^5.0.0",
"file-url": "^3.0.0",
"macos-version": "^5.2.1",
"tempy": "^1.0.0"
},
"devDependencies": {
"ava": "^1.2.1",
"delay": "^4.1.0",
"file-type": "^10.8.0",
"read-chunk": "^3.0.0",
"xo": "^0.24.0"
"ava": "^2.4.0",
"delay": "^5.0.0",
"file-type": "^12.0.0",
"read-chunk": "^3.2.0",
"tsd": "^0.14.0",
"xo": "^0.38.2"
},

@@ -35,0 +37,0 @@ "xo": {

@@ -1,6 +0,5 @@

# aperture-node [![Build Status](https://travis-ci.org/wulkano/aperture-node.svg?branch=master)](https://travis-ci.org/wulkano/aperture-node)
# aperture-node
> Record the screen on macOS from Node.js
## Install

@@ -12,5 +11,4 @@

*Requires macOS 10.12 or later.*
*Requires macOS 10.13 or later.*
## Usage

@@ -42,3 +40,2 @@

## API

@@ -74,3 +71,3 @@

Get a list of available video codecs. The key is the `videoCodec` option name and the value is the codec name. It only returns `hevc` if you're on macOS 10.13 or newer and your computer supports HEVC hardware encoding.
Get a list of available video codecs. The key is the `videoCodec` option name and the value is the codec name. It only returns `hevc` if your computer supports HEVC hardware encoding.

@@ -90,8 +87,30 @@ Example:

#### recorder.startRecording([[options]](#options))
#### recorder.startRecording([options?](#options))
Returns a `Promise` for the path to the screen recording file.
Returns a `Promise` that fullfills when the recording starts or rejects if the recording didn't start after 5 seconds.
Fullfills when the recording starts or rejects if the recording didn't start after 5 seconds.
#### recorder.isFileReady
`Promise` that fullfills with the path to the screen recording file when it's ready. This will never reject.
Only available while a recording is happening, `undefined` otherwise.
Usually, this resolves around 1 second before the recording starts, but that's not guaranteed.
#### recorder.pause()
Pauses the recording. To resume, call `recorder.resume()`.
Returns a `Promise` that fullfills when the recording has been paused.
#### recorder.resume()
Resumes the recording if it's been paused.
Returns a `Promise` that fullfills when the recording has been resumed.
#### recorder.isPaused()
Returns a `Promise` that resolves with a boolean indicating whether or not the recording is currently paused.
#### recorder.stopRecording()

@@ -103,5 +122,7 @@

Type: `object`
#### fps
Type: `number`<br>
Type: `number`\
Default: `30`

@@ -113,3 +134,3 @@

Type: `Object`<br>
Type: `object`\
Default: `undefined`

@@ -121,3 +142,3 @@

Type: `boolean`<br>
Type: `boolean`\
Default: `true`

@@ -129,3 +150,3 @@

Type: `boolean`<br>
Type: `boolean`\
Default: `false`

@@ -139,3 +160,3 @@

Type: `number`<br>
Type: `number`\
Default: `aperture.screens()[0]` *(Primary screen)*

@@ -147,3 +168,3 @@

Type: `string`<br>
Type: `string`\
Default: `undefined`

@@ -155,11 +176,10 @@

Type: `string`<br>
Default: `h264`<br>
Values: `hevc` `h264` `proRes422` `proRes4444`
Type: `string`\
Default: `'h264'`\
Values: `'hevc' | 'h264' | 'proRes422' | 'proRes4444'`
The `hevc` codec requires macOS 10.13 or newer. A computer with Intel 6th generation processor or newer is strongly recommended, as otherwise it will use software encoding, which only produces 3 FPS fullscreen recording.
A computer with Intel 6th generation processor or newer is strongly recommended for the `hevc` codec, as otherwise it will use software encoding, which only produces 3 FPS fullscreen recording.
The [`proRes422` and `proRes4444`](https://documentation.apple.com/en/finalcutpro/professionalformatsandworkflows/index.html#chapter=10%26section=2%26tasks=true) codecs are uncompressed data. They will create huge files.
## Why

@@ -181,10 +201,4 @@

## Related
- [Aperture](https://github.com/wulkano/Aperture) - The Swift framework used in this package
## License
MIT

Sorry, the diff of this file is not supported yet