Fix script deletion during execution on desktop
This commit fixes an issue seen on certain Windows environments (Windows 10 22H2 and 11 23H2 Pro Azure VMs) where scripts were being deleted during execution due to temporary directory usage. To resolve this, scripts are now stored in a persistent directory, enhancing reliability for long-running scripts and improving auditability along with troubleshooting. Key changes: - Move script execution logic to the `main` process from `preloader` to utilize Electron's `app.getPath`. - Improve runtime environment detection for non-browser environments to allow its usage in Electron main process. - Introduce a secure module to expose IPC channels from the main process to the renderer via the preloader process. Supporting refactorings include: - Simplify `CodeRunner` interface by removing the `tempScriptFolderName` parameter. - Rename `NodeSystemOperations` to `NodeElectronSystemOperations` as it now wraps electron APIs too, and convert it to class for simplicity. - Rename `TemporaryFileCodeRunner` to `ScriptFileCodeRunner` to reflect its new functinoality. - Rename `SystemOperations` folder to `System` for simplicity. - Rename `HostRuntimeEnvironment` to `BrowserRuntimeEnvironment` for clarity. - Refactor main Electron process configuration to align with latest Electron documentation/recommendations. - Refactor unit tests `BrowserRuntimeEnvironment` to simplify singleton workaround. - Use alias imports like `electron/main` and `electron/common` for better clarity.
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { BrowserCondition, TouchSupportExpectation } from '@/infrastructure/RuntimeEnvironment/Browser/BrowserOs/BrowserCondition';
|
||||
import { ConditionBasedOsDetector } from '@/infrastructure/RuntimeEnvironment/Browser/BrowserOs/ConditionBasedOsDetector';
|
||||
import { getAbsentStringTestCases, itEachAbsentCollectionValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { BrowserEnvironmentStub } from '@tests/unit/shared/Stubs/BrowserEnvironmentStub';
|
||||
import { BrowserConditionStub } from '@tests/unit/shared/Stubs/BrowserConditionStub';
|
||||
import { EnumRangeTestRunner } from '@tests/unit/application/Common/EnumRangeTestRunner';
|
||||
|
||||
describe('ConditionBasedOsDetector', () => {
|
||||
describe('constructor', () => {
|
||||
describe('throws when given no conditions', () => {
|
||||
itEachAbsentCollectionValue<BrowserCondition>((absentCollection) => {
|
||||
// arrange
|
||||
const expectedError = 'empty conditions';
|
||||
const conditions = absentCollection;
|
||||
// act
|
||||
const act = () => new ConditionBasedOsDetectorBuilder()
|
||||
.withConditions(conditions)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
}, { excludeUndefined: true, excludeNull: true });
|
||||
});
|
||||
it('throws if user agent part is missing', () => {
|
||||
// arrange
|
||||
const expectedError = 'Each condition must include at least one identifiable part of the user agent string.';
|
||||
const invalidCondition = new BrowserConditionStub().withExistingPartsInSameUserAgent([]);
|
||||
// act
|
||||
const act = () => new ConditionBasedOsDetectorBuilder()
|
||||
.withConditions([invalidCondition])
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('validates touch support expectation range', () => {
|
||||
// arrange
|
||||
const validValue = TouchSupportExpectation.MustExist;
|
||||
// act
|
||||
const act = (touchSupport: TouchSupportExpectation) => new ConditionBasedOsDetectorBuilder()
|
||||
.withConditions([new BrowserConditionStub().withTouchSupport(touchSupport)])
|
||||
.build();
|
||||
// assert
|
||||
new EnumRangeTestRunner(act)
|
||||
.testOutOfRangeThrows()
|
||||
.testValidValueDoesNotThrow(validValue);
|
||||
});
|
||||
it('throws if duplicate parts exist in user agent', () => {
|
||||
// arrange
|
||||
const expectedError = 'Found duplicate entries in user agent parts: Windows. Each part should be unique.';
|
||||
const invalidCondition = {
|
||||
operatingSystem: OperatingSystem.Windows,
|
||||
existingPartsInSameUserAgent: ['Windows', 'Windows'],
|
||||
};
|
||||
// act
|
||||
const act = () => new ConditionBasedOsDetectorBuilder()
|
||||
.withConditions([invalidCondition])
|
||||
.build();
|
||||
// assert
|
||||
expect(act).toThrowError(expectedError);
|
||||
});
|
||||
it('throws if duplicate non-existing parts exist in user agent', () => {
|
||||
// arrange
|
||||
const expectedError = 'Found duplicate entries in user agent parts: Linux. Each part should be unique.';
|
||||
const invalidCondition = {
|
||||
operatingSystem: OperatingSystem.Linux,
|
||||
existingPartsInSameUserAgent: ['Linux'],
|
||||
notExistingPartsInUserAgent: ['Linux'],
|
||||
};
|
||||
// act
|
||||
const act = () => new ConditionBasedOsDetectorBuilder()
|
||||
.withConditions([invalidCondition])
|
||||
.build();
|
||||
// assert
|
||||
expect(act).toThrowError(expectedError);
|
||||
});
|
||||
it('throws if duplicates found in any user agent parts', () => {
|
||||
// arrange
|
||||
const expectedError = 'Found duplicate entries in user agent parts: Android. Each part should be unique.';
|
||||
const invalidCondition = {
|
||||
operatingSystem: OperatingSystem.Android,
|
||||
existingPartsInSameUserAgent: ['Android'],
|
||||
notExistingPartsInUserAgent: ['iOS', 'Android'],
|
||||
};
|
||||
// act
|
||||
const act = () => new ConditionBasedOsDetectorBuilder()
|
||||
.withConditions([invalidCondition])
|
||||
.build();
|
||||
// assert
|
||||
expect(act).toThrowError(expectedError);
|
||||
});
|
||||
});
|
||||
describe('detect', () => {
|
||||
it('detects the correct OS when multiple conditions match', () => {
|
||||
// arrange
|
||||
const expectedOperatingSystem = OperatingSystem.Linux;
|
||||
const testUserAgent = 'test-user-agent';
|
||||
const expectedCondition = new BrowserConditionStub()
|
||||
.withOperatingSystem(expectedOperatingSystem)
|
||||
.withExistingPartsInSameUserAgent([testUserAgent]);
|
||||
const conditions = [
|
||||
expectedCondition,
|
||||
new BrowserConditionStub()
|
||||
.withExistingPartsInSameUserAgent(['unrelated user agent'])
|
||||
.withOperatingSystem(OperatingSystem.Android),
|
||||
new BrowserConditionStub()
|
||||
.withNotExistingPartsInUserAgent([testUserAgent])
|
||||
.withOperatingSystem(OperatingSystem.macOS),
|
||||
];
|
||||
const environment = new BrowserEnvironmentStub()
|
||||
.withUserAgent(testUserAgent);
|
||||
const detector = new ConditionBasedOsDetectorBuilder()
|
||||
.withConditions(conditions)
|
||||
.build();
|
||||
// act
|
||||
const actualOperatingSystem = detector.detect(environment);
|
||||
// assert
|
||||
expect(actualOperatingSystem).to.equal(expectedOperatingSystem);
|
||||
});
|
||||
|
||||
describe('user agent checks', () => {
|
||||
const testScenarios: ReadonlyArray<{
|
||||
readonly description: string;
|
||||
readonly buildEnvironment: (environment: BrowserEnvironmentStub) => BrowserEnvironmentStub;
|
||||
readonly buildCondition: (condition: BrowserConditionStub) => BrowserConditionStub;
|
||||
readonly detects: boolean;
|
||||
}> = [
|
||||
...getAbsentStringTestCases({ excludeUndefined: true, excludeNull: true })
|
||||
.map((testCase) => ({
|
||||
description: `does not detect when user agent is empty (${testCase.valueName})`,
|
||||
buildEnvironment: (environment) => environment.withUserAgent(testCase.absentValue),
|
||||
buildCondition: (condition) => condition,
|
||||
detects: false,
|
||||
})),
|
||||
{
|
||||
description: 'detects when user agent matches completely',
|
||||
buildEnvironment: (environment) => environment.withUserAgent('test-user-agent'),
|
||||
buildCondition: (condition) => condition.withExistingPartsInSameUserAgent(['test-user-agent']),
|
||||
detects: true,
|
||||
},
|
||||
{
|
||||
description: 'detects when substring of user agent exists',
|
||||
buildEnvironment: (environment) => environment.withUserAgent('test-user-agent'),
|
||||
buildCondition: (condition) => condition.withExistingPartsInSameUserAgent(['test']),
|
||||
detects: true,
|
||||
},
|
||||
{
|
||||
description: 'does not detect when no part of user agent exists',
|
||||
buildEnvironment: (environment) => environment.withUserAgent('unrelated-user-agent'),
|
||||
buildCondition: (condition) => condition.withExistingPartsInSameUserAgent(['lorem-ipsum']),
|
||||
detects: false,
|
||||
},
|
||||
{
|
||||
description: 'detects when non-existing parts do not match',
|
||||
buildEnvironment: (environment) => environment.withUserAgent('1-3'),
|
||||
buildCondition: (condition) => condition.withExistingPartsInSameUserAgent(['1']).withNotExistingPartsInUserAgent(['2']),
|
||||
detects: true,
|
||||
},
|
||||
{
|
||||
description: 'does not detect when non-existing and existing parts match',
|
||||
buildEnvironment: (environment) => environment.withUserAgent('1-2'),
|
||||
buildCondition: (condition) => condition.withExistingPartsInSameUserAgent(['1']).withNotExistingPartsInUserAgent(['2']),
|
||||
detects: false,
|
||||
},
|
||||
];
|
||||
testScenarios.forEach(({
|
||||
description, buildEnvironment, buildCondition, detects,
|
||||
}) => {
|
||||
it(description, () => {
|
||||
// arrange
|
||||
const environment = buildEnvironment(new BrowserEnvironmentStub());
|
||||
const condition = buildCondition(
|
||||
new BrowserConditionStub().withOperatingSystem(OperatingSystem.Linux),
|
||||
);
|
||||
const detector = new ConditionBasedOsDetectorBuilder()
|
||||
.withConditions([condition])
|
||||
.build();
|
||||
// act
|
||||
const actualOperatingSystem = detector.detect(environment);
|
||||
// assert
|
||||
expect(actualOperatingSystem !== undefined).to.equal(detects);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('touch support checks', () => {
|
||||
const testScenarios: ReadonlyArray<{
|
||||
readonly description: string;
|
||||
readonly expectation: TouchSupportExpectation;
|
||||
readonly isTouchSupportInEnvironment: boolean;
|
||||
readonly detects: boolean;
|
||||
}> = [
|
||||
{
|
||||
description: 'detects when touch support exists and is expected',
|
||||
expectation: TouchSupportExpectation.MustExist,
|
||||
isTouchSupportInEnvironment: true,
|
||||
detects: true,
|
||||
},
|
||||
{
|
||||
description: 'does not detect when touch support does not exists but is expected',
|
||||
expectation: TouchSupportExpectation.MustExist,
|
||||
isTouchSupportInEnvironment: false,
|
||||
detects: false,
|
||||
},
|
||||
{
|
||||
description: 'detects when touch support does not exist and is not expected',
|
||||
expectation: TouchSupportExpectation.MustNotExist,
|
||||
isTouchSupportInEnvironment: false,
|
||||
detects: true,
|
||||
},
|
||||
{
|
||||
description: 'does not detect when touch support exists but is not expected',
|
||||
expectation: TouchSupportExpectation.MustNotExist,
|
||||
isTouchSupportInEnvironment: true,
|
||||
detects: false,
|
||||
},
|
||||
];
|
||||
testScenarios.forEach(({
|
||||
description, expectation, isTouchSupportInEnvironment, detects,
|
||||
}) => {
|
||||
it(description, () => {
|
||||
// arrange
|
||||
const userAgent = 'iPhone';
|
||||
const environment = new BrowserEnvironmentStub()
|
||||
.withUserAgent(userAgent)
|
||||
.withIsTouchSupported(isTouchSupportInEnvironment);
|
||||
const conditionWithTouchSupport = new BrowserConditionStub()
|
||||
.withExistingPartsInSameUserAgent([userAgent])
|
||||
.withTouchSupport(expectation);
|
||||
const detector = new ConditionBasedOsDetectorBuilder()
|
||||
.withConditions([conditionWithTouchSupport])
|
||||
.build();
|
||||
// act
|
||||
const actualOperatingSystem = detector.detect(environment);
|
||||
// assert
|
||||
expect(actualOperatingSystem !== undefined)
|
||||
.to.equal(detects);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class ConditionBasedOsDetectorBuilder {
|
||||
private conditions: readonly BrowserCondition[] = [{
|
||||
operatingSystem: OperatingSystem.iOS,
|
||||
existingPartsInSameUserAgent: ['iPhone'],
|
||||
}];
|
||||
|
||||
public withConditions(conditions: readonly BrowserCondition[]): this {
|
||||
this.conditions = conditions;
|
||||
return this;
|
||||
}
|
||||
|
||||
public build(): ConditionBasedOsDetector {
|
||||
return new ConditionBasedOsDetector(
|
||||
this.conditions,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
// eslint-disable-next-line max-classes-per-file
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { BrowserOsDetector } from '@/infrastructure/RuntimeEnvironment/Browser/BrowserOs/BrowserOsDetector';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { BrowserRuntimeEnvironment } from '@/infrastructure/RuntimeEnvironment/Browser/BrowserRuntimeEnvironment';
|
||||
import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import { BrowserOsDetectorStub } from '@tests/unit/shared/Stubs/BrowserOsDetectorStub';
|
||||
import { IEnvironmentVariables } from '@/infrastructure/EnvironmentVariables/IEnvironmentVariables';
|
||||
import { EnvironmentVariablesStub } from '@tests/unit/shared/Stubs/EnvironmentVariablesStub';
|
||||
import { expectExists } from '@tests/shared/Assertions/ExpectExists';
|
||||
|
||||
describe('BrowserRuntimeEnvironment', () => {
|
||||
describe('ctor', () => {
|
||||
describe('throws if window is absent', () => {
|
||||
itEachAbsentObjectValue((absentValue) => {
|
||||
// arrange
|
||||
const expectedError = 'missing window';
|
||||
const absentWindow = absentValue;
|
||||
// act
|
||||
const act = () => new BrowserRuntimeEnvironmentBuilder()
|
||||
.withWindow(absentWindow as never)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
it('uses browser OS detector with current touch support', () => {
|
||||
// arrange
|
||||
const expectedTouchSupport = true;
|
||||
const osDetector = new BrowserOsDetectorStub();
|
||||
const window = { os: undefined, navigator: { userAgent: 'Forcing touch detection' } } as Partial<Window>;
|
||||
// act
|
||||
new BrowserRuntimeEnvironmentBuilder()
|
||||
.withWindow(window)
|
||||
.withBrowserOsDetector(osDetector)
|
||||
.withTouchSupported(expectedTouchSupport)
|
||||
.build();
|
||||
// assert
|
||||
const actualCall = osDetector.callHistory.find((c) => c.methodName === 'detect');
|
||||
expectExists(actualCall);
|
||||
const [{ isTouchSupported: actualTouchSupport }] = actualCall.args;
|
||||
expect(actualTouchSupport).to.equal(expectedTouchSupport);
|
||||
});
|
||||
});
|
||||
describe('isDesktop', () => {
|
||||
it('returns true when window property isDesktop is true', () => {
|
||||
// arrange
|
||||
const desktopWindow = {
|
||||
isDesktop: true,
|
||||
};
|
||||
// act
|
||||
const sut = new BrowserRuntimeEnvironmentBuilder()
|
||||
.withWindow(desktopWindow)
|
||||
.build();
|
||||
// assert
|
||||
expect(sut.isDesktop).to.equal(true);
|
||||
});
|
||||
it('returns false when window property isDesktop is false', () => {
|
||||
// arrange
|
||||
const expectedValue = false;
|
||||
const browserWindow = {
|
||||
isDesktop: false,
|
||||
};
|
||||
// act
|
||||
const sut = new BrowserRuntimeEnvironmentBuilder()
|
||||
.withWindow(browserWindow)
|
||||
.build();
|
||||
// assert
|
||||
expect(sut.isDesktop).to.equal(expectedValue);
|
||||
});
|
||||
});
|
||||
describe('os', () => {
|
||||
it('returns undefined if user agent is missing', () => {
|
||||
// arrange
|
||||
const expected = undefined;
|
||||
const browserDetectorMock: BrowserOsDetector = {
|
||||
detect: () => {
|
||||
throw new Error('should not reach here');
|
||||
},
|
||||
};
|
||||
const sut = new BrowserRuntimeEnvironmentBuilder()
|
||||
.withBrowserOsDetector(browserDetectorMock)
|
||||
.build();
|
||||
// act
|
||||
const actual = sut.os;
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
it('gets browser os from BrowserOsDetector', () => {
|
||||
// arrange
|
||||
const givenUserAgent = 'testUserAgent';
|
||||
const expected = OperatingSystem.macOS;
|
||||
const windowWithUserAgent = {
|
||||
navigator: {
|
||||
userAgent: givenUserAgent,
|
||||
},
|
||||
};
|
||||
const browserDetectorMock: BrowserOsDetector = {
|
||||
detect: (environment) => {
|
||||
if (environment.userAgent !== givenUserAgent) {
|
||||
throw new Error('Unexpected user agent');
|
||||
}
|
||||
return expected;
|
||||
},
|
||||
};
|
||||
// act
|
||||
const sut = new BrowserRuntimeEnvironmentBuilder()
|
||||
.withWindow(windowWithUserAgent as Partial<Window>)
|
||||
.withBrowserOsDetector(browserDetectorMock)
|
||||
.build();
|
||||
const actual = sut.os;
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
describe('desktop os', () => {
|
||||
describe('returns from window property `os`', () => {
|
||||
const testValues = [
|
||||
OperatingSystem.macOS,
|
||||
OperatingSystem.Windows,
|
||||
OperatingSystem.Linux,
|
||||
];
|
||||
testValues.forEach((testValue) => {
|
||||
it(`given ${OperatingSystem[testValue]}`, () => {
|
||||
// arrange
|
||||
const expectedOs = testValue;
|
||||
const desktopWindowWithOs = {
|
||||
isDesktop: true,
|
||||
os: expectedOs,
|
||||
};
|
||||
// act
|
||||
const sut = new BrowserRuntimeEnvironmentBuilder()
|
||||
.withWindow(desktopWindowWithOs)
|
||||
.build();
|
||||
// assert
|
||||
const actualOs = sut.os;
|
||||
expect(actualOs).to.equal(expectedOs);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('returns undefined when window property `os` is absent', () => {
|
||||
itEachAbsentObjectValue((absentValue) => {
|
||||
// arrange
|
||||
const expectedValue = undefined;
|
||||
const windowWithAbsentOs = {
|
||||
os: absentValue as never,
|
||||
};
|
||||
// act
|
||||
const sut = new BrowserRuntimeEnvironmentBuilder()
|
||||
.withWindow(windowWithAbsentOs)
|
||||
.build();
|
||||
// assert
|
||||
expect(sut.os).to.equal(expectedValue);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('isNonProduction', () => {
|
||||
[true, false].forEach((value) => {
|
||||
it(`sets ${value} from environment variables`, () => {
|
||||
// arrange
|
||||
const expectedValue = value;
|
||||
const environment = new EnvironmentVariablesStub()
|
||||
.withIsNonProduction(expectedValue);
|
||||
// act
|
||||
const sut = new BrowserRuntimeEnvironmentBuilder()
|
||||
.withEnvironmentVariables(environment)
|
||||
.build();
|
||||
// assert
|
||||
const actualValue = sut.isNonProduction;
|
||||
expect(actualValue).to.equal(expectedValue);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class BrowserRuntimeEnvironmentBuilder {
|
||||
private window: Partial<Window> = {};
|
||||
|
||||
private browserOsDetector: BrowserOsDetector = new BrowserOsDetectorStub();
|
||||
|
||||
private environmentVariables: IEnvironmentVariables = new EnvironmentVariablesStub();
|
||||
|
||||
private isTouchSupported = false;
|
||||
|
||||
public withEnvironmentVariables(environmentVariables: IEnvironmentVariables): this {
|
||||
this.environmentVariables = environmentVariables;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withWindow(window: Partial<Window>): this {
|
||||
this.window = window;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withBrowserOsDetector(browserOsDetector: BrowserOsDetector): this {
|
||||
this.browserOsDetector = browserOsDetector;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withTouchSupported(isTouchSupported: boolean): this {
|
||||
this.isTouchSupported = isTouchSupported;
|
||||
return this;
|
||||
}
|
||||
|
||||
public build() {
|
||||
return new BrowserRuntimeEnvironment(
|
||||
this.window,
|
||||
this.environmentVariables,
|
||||
this.browserOsDetector,
|
||||
() => this.isTouchSupported,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { BrowserTouchSupportAccessor, isTouchEnabledDevice } from '@/infrastructure/RuntimeEnvironment/Browser/TouchSupportDetection';
|
||||
|
||||
describe('TouchSupportDetection', () => {
|
||||
describe('isTouchEnabledDevice', () => {
|
||||
const testScenarios: ReadonlyArray<{
|
||||
readonly description: string;
|
||||
readonly accessor: BrowserTouchSupportAccessor;
|
||||
readonly expectedTouch: boolean;
|
||||
}> = [
|
||||
{
|
||||
description: 'detects no touch capabilities',
|
||||
accessor: createMockAccessor(),
|
||||
expectedTouch: false,
|
||||
},
|
||||
{
|
||||
description: 'detects touch capability with defined document.ontouchend',
|
||||
accessor: createMockAccessor({ documentOntouchend: () => 'not-undefined' }),
|
||||
expectedTouch: true,
|
||||
},
|
||||
{
|
||||
description: 'detects touch capability with navigator.maxTouchPoints > 0',
|
||||
accessor: createMockAccessor({ navigatorMaxTouchPoints: () => 1 }),
|
||||
expectedTouch: true,
|
||||
},
|
||||
{
|
||||
description: 'detects touch capability when matchMedia for pointer coarse is true',
|
||||
accessor: createMockAccessor({
|
||||
windowMatchMediaMatches: (query: string) => {
|
||||
return query === '(any-pointer: coarse)';
|
||||
},
|
||||
}),
|
||||
expectedTouch: true,
|
||||
},
|
||||
];
|
||||
testScenarios.forEach(({ description, accessor, expectedTouch }) => {
|
||||
it(`${description} - returns ${expectedTouch}`, () => {
|
||||
// act
|
||||
const isTouchDetected = isTouchEnabledDevice(accessor);
|
||||
// assert
|
||||
expect(isTouchDetected).to.equal(expectedTouch);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createMockAccessor(
|
||||
touchSupportFeatures: Partial<BrowserTouchSupportAccessor> = {},
|
||||
): BrowserTouchSupportAccessor {
|
||||
const defaultTouchSupport: BrowserTouchSupportAccessor = {
|
||||
navigatorMaxTouchPoints: () => undefined,
|
||||
windowMatchMediaMatches: () => false,
|
||||
documentOntouchend: () => undefined,
|
||||
};
|
||||
return {
|
||||
...defaultTouchSupport,
|
||||
...touchSupportFeatures,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user