diff --git a/common.eslintrc.js b/common.eslintrc.js index 1200366b4..5b4fc4b8a 100644 --- a/common.eslintrc.js +++ b/common.eslintrc.js @@ -1,3 +1,5 @@ +// Recommended: https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/src/configs/recommended-type-checked.ts + module.exports = { root: true, parser: '@typescript-eslint/parser', @@ -9,15 +11,25 @@ module.exports = { plugins: ['@typescript-eslint', 'prettier'], ignorePatterns: ['**/*.spec.ts'], rules: { - '@typescript-eslint/restrict-template-expressions': 'error', - 'standard/no-callback-literal': 0, // Disable this as we have too many callbacks relying on literals - 'no-throw-literal': 0, - camelcase: 'off', - 'sort-imports': 'off', - 'eol-last': 'error', - 'no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0 }], - 'no-trailing-spaces': 'error', + 'prettier/prettier': [ + 'error', + { + singleQuote: true, + trailingComma: 'all', + printWidth: 120, + semi: false, + }, + { + usePrettierrc: false, + }, + ], '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-for-in-array': 'error', + '@typescript-eslint/no-var-requires': 'error', + '@typescript-eslint/prefer-as-const': 'error', + '@typescript-eslint/no-array-constructor': 'error', + '@typescript-eslint/no-duplicate-enum-values': 'error', + '@typescript-eslint/restrict-template-expressions': 'error', '@typescript-eslint/no-unused-vars': [ 'error', { @@ -31,31 +43,26 @@ module.exports = { '@typescript-eslint/no-floating-promises': ['error'], 'block-scoped-var': 'error', 'comma-dangle': ['error', 'always-multiline'], - curly: ['error', 'all'], + 'eol-last': 'error', 'no-confusing-arrow': 'error', - 'no-inline-comments': 'warn', - 'no-invalid-this': 'error', - 'no-return-assign': 'warn', + 'no-console': ['warn', { allow: ['warn', 'error'] }], 'no-constructor-return': 'error', 'no-duplicate-imports': 'error', + 'no-inline-comments': 'warn', + 'no-invalid-this': 'error', + 'no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0 }], + 'no-return-assign': 'warn', 'no-self-compare': 'error', - 'no-console': ['warn', { allow: ['warn', 'error'] }], + 'no-throw-literal': 0, + 'no-trailing-spaces': 'error', 'no-unmodified-loop-condition': 'error', 'no-unused-private-class-members': 'error', 'object-curly-spacing': ['error', 'always'], + 'sort-imports': 'off', + 'standard/no-callback-literal': 0, // Disable this as we have too many callbacks relying on literals + camelcase: 'off', + curly: ['error', 'all'], quotes: ['error', 'single', { avoidEscape: true }], semi: ['error', 'never'], - 'prettier/prettier': [ - 'error', - { - singleQuote: true, - trailingComma: 'all', - printWidth: 120, - semi: false, - }, - { - usePrettierrc: false, - }, - ], }, } diff --git a/packages/mobile/ios/Podfile.lock b/packages/mobile/ios/Podfile.lock index 077101e8b..c9e0c77bd 100644 --- a/packages/mobile/ios/Podfile.lock +++ b/packages/mobile/ios/Podfile.lock @@ -723,7 +723,7 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - boost: 57d2868c099736d80fcd648bf211b4431e51a558 + boost: a7c83b31436843459a1961bfd74b96033dc77234 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 FBLazyVector: 4cce221dd782d3ff7c4172167bba09d58af67ccb @@ -743,7 +743,7 @@ SPEC CHECKSUMS: MMKV: 9c4663aa7ca255d478ff10f2f5cb7d17c1651ccd MMKVCore: 89f5c8a66bba2dcd551779dea4d412eeec8ff5bb OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c - RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1 + RCT-Folly: 0080d0a6ebf2577475bda044aa59e2ca1f909cda RCTRequired: a2faf4bad4e438ca37b2040cb8f7799baa065c18 RCTTypeSafety: cb09f3e4747b6d18331a15eb05271de7441ca0b3 React: 13109005b5353095c052f26af37413340ccf7a5d diff --git a/packages/mobile/ios/StandardNotes.xcodeproj/project.pbxproj b/packages/mobile/ios/StandardNotes.xcodeproj/project.pbxproj index 18358aca0..987426dbe 100644 --- a/packages/mobile/ios/StandardNotes.xcodeproj/project.pbxproj +++ b/packages/mobile/ios/StandardNotes.xcodeproj/project.pbxproj @@ -15,7 +15,7 @@ 07CAB7702A618385008FE1EF /* ReceiveSharingIntent.m in Sources */ = {isa = PBXBuildFile; fileRef = 07CAB76F2A618385008FE1EF /* ReceiveSharingIntent.m */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; - 1C2EEB3B45F4EB07AC795C77 /* (null) in Frameworks */ = {isa = PBXBuildFile; }; + 1C2EEB3B45F4EB07AC795C77 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; }; 33BB1B14071EBE5978EBF3A8 /* libPods-StandardNotes-StandardNotesTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 04FCB5A3A3387CA3CFC82AA3 /* libPods-StandardNotes-StandardNotesTests.a */; }; BC8DEA834BF198E8511F04FF /* libPods-StandardNotesDev.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 51F2D747BE02C2A1BCFEEFD1 /* libPods-StandardNotesDev.a */; }; CD6592A9291EEFCC00C09DC6 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD6592A8291EEFCC00C09DC6 /* StoreKit.framework */; }; @@ -123,7 +123,7 @@ buildActionMask = 2147483647; files = ( CD6592A9291EEFCC00C09DC6 /* StoreKit.framework in Frameworks */, - 1C2EEB3B45F4EB07AC795C77 /* (null) in Frameworks */, + 1C2EEB3B45F4EB07AC795C77 /* BuildFile in Frameworks */, DD3D1CE428EC1C8BA0C49211 /* libPods-StandardNotes.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/packages/models/src/Domain/Runtime/Feature/UIFeature.ts b/packages/models/src/Domain/Runtime/Feature/UIFeature.ts index 18abf2409..1a2b2c378 100644 --- a/packages/models/src/Domain/Runtime/Feature/UIFeature.ts +++ b/packages/models/src/Domain/Runtime/Feature/UIFeature.ts @@ -2,6 +2,7 @@ import { ComponentArea, ComponentPermission, EditorFeatureDescription, + FindNativeFeature, NativeFeatureIdentifier, NoteType, ThemeDockIcon, @@ -47,6 +48,10 @@ export class UIFeature implements UIFeature throw new Error('Cannot cast item to feature description') } + get isNativeFeature(): boolean { + return FindNativeFeature(this.featureIdentifier) !== undefined + } + get uniqueIdentifier(): NativeFeatureIdentifier | Uuid { if (isNativeFeature(this.item)) { const nativeFeature = NativeFeatureIdentifier.create(this.item.identifier) diff --git a/packages/models/src/Domain/Runtime/Feature/UIFeatureInterface.ts b/packages/models/src/Domain/Runtime/Feature/UIFeatureInterface.ts index 894d2b2d3..da1d1af95 100644 --- a/packages/models/src/Domain/Runtime/Feature/UIFeatureInterface.ts +++ b/packages/models/src/Domain/Runtime/Feature/UIFeatureInterface.ts @@ -17,6 +17,7 @@ export interface UIFeatureInterface { get isThemeComponent(): boolean get asComponent(): ComponentInterface get asFeatureDescription(): F + get isNativeFeature(): boolean get uniqueIdentifier(): NativeFeatureIdentifier | Uuid get featureIdentifier(): string get noteType(): NoteType diff --git a/packages/snjs/lib/Services/ComponentManager/ComponentViewer.ts b/packages/snjs/lib/Services/ComponentManager/ComponentViewer.ts index d3ea572fb..63a6ce7f4 100644 --- a/packages/snjs/lib/Services/ComponentManager/ComponentViewer.ts +++ b/packages/snjs/lib/Services/ComponentManager/ComponentViewer.ts @@ -458,8 +458,7 @@ export class ComponentViewer implements ComponentViewerInterface { } this.log('Send message to component', this.componentOrFeature, 'message: ', message) - let origin = this.options.url - if (!origin || !this.window) { + if (!this.window) { if (essential) { void this.services.alerts.alert( `Standard Notes is trying to communicate with ${this.componentOrFeature.displayName}, ` + @@ -469,13 +468,11 @@ export class ComponentViewer implements ComponentViewerInterface { return } - if (!origin.startsWith('http') && !origin.startsWith('file')) { - /* Native extension running in web, prefix current host */ - origin = window.location.href + origin - } + /** Because iframes do not allow-same-origin, their origin is `null`, and so we can't target their explicit origin */ + const nullOrigin = '*' /* Mobile messaging requires json */ - this.window.postMessage(this.isMobile ? JSON.stringify(message) : message, origin) + this.window.postMessage(this.isMobile ? JSON.stringify(message) : message, nullOrigin) } private responseItemsByRemovingPrivateProperties( diff --git a/packages/web/src/javascripts/Components/ComponentView/IframeFeatureView.tsx b/packages/web/src/javascripts/Components/ComponentView/IframeFeatureView.tsx index 1361060e9..b2fc00588 100644 --- a/packages/web/src/javascripts/Components/ComponentView/IframeFeatureView.tsx +++ b/packages/web/src/javascripts/Components/ComponentView/IframeFeatureView.tsx @@ -7,7 +7,7 @@ import { ComponentInterface, SubscriptionManagerEvent, } from '@standardnotes/snjs' -import { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react' +import { FunctionComponent, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { observer } from 'mobx-react-lite' import OfflineRestricted from '@/Components/ComponentView/OfflineRestricted' import UrlMissing from '@/Components/ComponentView/UrlMissing' @@ -178,6 +178,37 @@ const IframeFeatureView: FunctionComponent = ({ onLoad, componentViewer, } }, [application, requestReload, componentViewer, uiFeature]) + const sandboxAttributes = useMemo(() => { + const attributes = [ + 'allow-scripts', + 'allow-top-navigation-by-user-activation', + 'allow-popups', + 'allow-modals', + 'allow-forms', + 'allow-downloads', + ] + + if (uiFeature.isNativeFeature) { + attributes.push('allow-popups-to-escape-sandbox') + } + + if (application.isNativeMobileWeb()) { + /** + * Native mobile web serves native components through the file:// protocol. + * Native mobile web also does not use localStorage, unlike the web app. + * So, components served through the file:// (precompiled editors) will be treated + * as same origin as the parent app, but will not have meaningful access. + * + * Third party components will have a non-file:// origin, and thus don't need this attribute. + */ + if (uiFeature.isNativeFeature) { + attributes.push('allow-same-origin') + } + } + + return attributes + }, [application, uiFeature]) + return ( <> {hasIssueLoading && ( @@ -208,7 +239,7 @@ const IframeFeatureView: FunctionComponent = ({ onLoad, componentViewer, data-component-viewer-id={componentViewer.identifier} frameBorder={0} src={componentViewer.url || ''} - sandbox="allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-modals allow-forms allow-downloads" + sandbox={sandboxAttributes.join(' ')} > Loading diff --git a/packages/web/src/javascripts/Components/U2FAuthIframe/U2FAuthIframe.tsx b/packages/web/src/javascripts/Components/U2FAuthIframe/U2FAuthIframe.tsx index 156762589..35f1e8080 100644 --- a/packages/web/src/javascripts/Components/U2FAuthIframe/U2FAuthIframe.tsx +++ b/packages/web/src/javascripts/Components/U2FAuthIframe/U2FAuthIframe.tsx @@ -89,8 +89,8 @@ const U2FAuthIframe = () => { if (!error) { return } - setError(error.toString()) - console.error(error.toString()) + setError(JSON.stringify(error)) + console.error(error) } }, [source, username, apiHost]) diff --git a/packages/web/web.webpack.dev.js b/packages/web/web.webpack.dev.js index c5313007d..6eccd8573 100644 --- a/packages/web/web.webpack.dev.js +++ b/packages/web/web.webpack.dev.js @@ -17,6 +17,9 @@ module.exports = (env, argv) => { }, plugins: [new ReactRefreshWebpackPlugin()], devServer: { + headers: { + 'Access-Control-Allow-Origin': '*', + }, hot: true, static: './dist', port,