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

Migrate to manifest V3 #189

Merged
merged 1 commit into from
Apr 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 2 additions & 13 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,12 @@ module.exports = {
globals: {
module: 'readonly',
CF_VERSION: 'readonly',
chrome: 'readonly',
},
overrides: [
{
files: ['*.spec.js'],
globals: {
test: 'readonly',
snapshot: 'readonly',
snapshotComponent: 'readonly',
shallow: 'readonly',
tMock: 'readonly',
it: 'readonly',
expect: 'readonly',
describe: 'readonly',
before: 'readonly',
beforeEach: 'readonly',
},
plugins: ['jest'],
extends: ['plugin:jest/recommended'],
},
],
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<p align="center"><img src="chrome/assets/img/icon.png" height="100"/></p>
<p align="center"><img src="app/assets/img/icon.png" height="100"/></p>
<h1 align="center"><a href="https://captainfact.io">CaptainFact.io</a></h1>

<table>
Expand Down
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
17 changes: 12 additions & 5 deletions app/components/Popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,18 @@ import translate from '../lib/translate'
import { linkToAddVideo } from '../lib/cf_urls'
import VideosList from './VideosList'
import ExternalLink from './ExternalLink'
import { BrowserExtension } from '../lib/browser-extension'

export default class Popup extends React.Component {
state = { url: null }

componentDidMount() {
chrome.tabs.query({ active: true, currentWindow: true }, (arrayOfTabs) => {
this.setState({ url: arrayOfTabs[0].url })
})
BrowserExtension.tabs.query(
{ active: true, currentWindow: true },
(arrayOfTabs) => {
this.setState({ url: arrayOfTabs[0].url })
}
)
}

render() {
Expand All @@ -29,7 +33,7 @@ export default class Popup extends React.Component {
className={styles.bannerLink}
>
<img
src={chrome.runtime.getURL('img/banner.jpg')}
src={BrowserExtension.runtime.getURL('img/banner.jpg')}
className={styles.banner}
alt="CaptainFact"
/>
Expand Down Expand Up @@ -81,7 +85,10 @@ export default class Popup extends React.Component {
className={styles.actionsBlock}
href={linkToAddVideo(url)}
>
<img src={chrome.runtime.getURL('img/new_tab.png')} alt="" />
<img
src={BrowserExtension.runtime.getURL('img/new_tab.png')}
alt=""
/>
{translate('openOnCF')}
</ExternalLink>
</div>
Expand Down
63 changes: 58 additions & 5 deletions app/components/Select.spec.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,63 @@
import React from 'react'
import renderer from 'react-test-renderer'
import Select from './Select.js'

test('render selected option', () => {
describe('ON/OFF options', () => {
const options = { ON: true, OFF: false }
snapshotComponent(<Select name="videosOverlay" selected options={options} />)
snapshotComponent(
<Select name="videosOverlay" selected={false} options={options} />
)

test('render with 1st option selected', () => {
expect(
renderer
.create(<Select name="videosOverlay" selected options={options} />)
.toJSON()
).toMatchInlineSnapshot(`
<div
className="select"
>
<a
className="button active"
data-value="ON"
onClick={[Function]}
>
ON
</a>
<a
className="button"
data-value="OFF"
onClick={[Function]}
>
OFF
</a>
</div>
`)
})

test('render with second option selected', () => {
expect(
renderer
.create(
<Select name="videosOverlay" selected={false} options={options} />
)
.toJSON()
).toMatchInlineSnapshot(`
<div
className="select"
>
<a
className="button"
data-value="ON"
onClick={[Function]}
>
ON
</a>
<a
className="button active"
data-value="OFF"
onClick={[Function]}
>
OFF
</a>
</div>
`)
})
})
47 changes: 0 additions & 47 deletions app/components/__snapshots__/Select.spec.js.snap

This file was deleted.

136 changes: 136 additions & 0 deletions app/entrypoints/inject.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import CaptainFactOverlayInjector from 'captain-fact-overlay-injector'
import { CF_API_URL } from '../lib/constants'
import { getVideoProvider } from '../lib/url_utils'
import LocalSettings from '../lib/local_settings'
import DataCache from '../lib/data_cache'
import { BrowserExtension } from '../lib/browser-extension'

console.info('[CaptainFact] Inject into video')

const SUPPORTED_LANGUAGES = ['en', 'fr']
const DEFAULT_LANGUAGE = SUPPORTED_LANGUAGES[0]
const LANGUAGE = BrowserExtension.i18n.getUILanguage().replace(/-.*/, '')

// TODO: Move to a MutationObserver
const waitForVideoPlayer = async (tries = 15) => {
if (tries === 0) {
console.error('[CaptainFact] Failed to find the video player in time')
return false
} else if (!document.getElementById('movie_player')) {
return new Promise((resolve) => {
setTimeout(() => resolve(waitForVideoPlayer(tries - 1)), 200)
})
} else {
return true
}
}

// Singleton injector. Will be set by `injectVideoOverlay`
let injector

const injectVideoOverlay = () => {
if (injector) {
console.warn('[CaptainFact] Already injected')
return
}

// Instantiate the injector with our custom configuration
injector = new CaptainFactOverlayInjector({
injector: {
videosSelector: () => [document.getElementById('movie_player')],
urlExtractor: () => location.href,
getPlayer: (video, adapters) =>
new adapters.HTML5(video.querySelector('video')),
},
app: {
baseSize: '16px',
language: SUPPORTED_LANGUAGES.includes(LANGUAGE)
? LANGUAGE
: DEFAULT_LANGUAGE,
graphics: {
logo: {
neutral: BrowserExtension.runtime.getURL('img/logo-borderless.svg'),
confirm: BrowserExtension.runtime.getURL(
'img/confirm-borderless.svg'
),
refute: BrowserExtension.runtime.getURL('img/refute-borderless.svg'),
},
newTab: BrowserExtension.runtime.getURL('img/new_tab.png'),
star: BrowserExtension.runtime.getURL('img/star.png'),
next: BrowserExtension.runtime.getURL('img/next.svg'),
prev: BrowserExtension.runtime.getURL('img/prev.svg'),
close: BrowserExtension.runtime.getURL('img/close.svg'),
},
},
services: {
apiURL: CF_API_URL,
},
})

// Watch for the theater mode button to make sure we update the overlay size/position after the resize
const theaterBtn = document.querySelector('.ytp-size-button')
if (theaterBtn) {
theaterBtn.addEventListener('click', () =>
setTimeout(injector.forceResize, 50)
)
}
}

/**
* Called when a route change is detected (and on first page load)
*/
const onPageLoad = async () => {
// Whatever happens, we want to disable the current overlay
injector?.disable()

// Stop if we're not on a supported URL
const videoInfo = getVideoProvider(window.location.href)
if (!videoInfo) {
return
}

// Other
const { provider, providerId } = videoInfo
const hasVideo = await DataCache.hasVideo(provider, providerId)
if (!hasVideo) {
return
}

console.debug('[CaptainFact] Video found, injecting facts 🌷')
if (await waitForVideoPlayer()) {
if (injector) {
injector.enable()
} else {
injectVideoOverlay()
}
} else {
console.error('[CaptainFact] Failed to find the video player in time')
}
}

/**
* Main entrypoint: inject if enabled in settings, and watch for settings changes
* to enable/disable overlay.
*/
LocalSettings.load().then((settings) => {
if (settings.videosOverlay) {
window.addEventListener('yt-navigate-finish', onPageLoad)
}

// Enable or disable overlay based on settings changes
LocalSettings.addChangeListener((oldParams, newParams) => {
if (oldParams.videosOverlay && !newParams.videosOverlay) {
injector?.disable()
window.removeEventListener('yt-navigate-finish', onPageLoad)
} else if (!oldParams.videosOverlay && newParams.videosOverlay) {
if (injector) {
injector.enable()
} else {
onPageLoad()
}

// Watch for URL changes to update the overlay
window.addEventListener('yt-navigate-finish', onPageLoad)
}
})
})
4 changes: 2 additions & 2 deletions chrome/extension/popup.js → app/entrypoints/popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import React from 'react'
import ReactDOM from 'react-dom'
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client'

import { CF_API_URL } from '../../app/lib/constants'
import Popup from '../../app/components/Popup'
import { CF_API_URL } from '../lib/constants'
import Popup from '../components/Popup'

// Configure Apollo client
const client = new ApolloClient({ uri: CF_API_URL, cache: new InMemoryCache() })
Expand Down
5 changes: 0 additions & 5 deletions app/lib/__snapshots__/cf_urls.spec.js.snap

This file was deleted.

8 changes: 8 additions & 0 deletions app/lib/browser-extension.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* We don't want to hardcode "chrome" everywhere in the codebase, so we export this alias instead
* See https://bugs.chromium.org/p/chromium/issues/detail?id=798169
*/

export const BrowserExtension =
// eslint-disable-next-line no-undef
typeof browser !== 'undefined' ? browser : chrome
16 changes: 10 additions & 6 deletions app/lib/browser_icon_badge_counter.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { BrowserExtension } from './browser-extension'
import LocalSettings from './local_settings'

export const MAX_VALUE = 99
Expand All @@ -23,11 +24,14 @@ export default class BrowserIconBadgeCounter {
return LocalSettings.getValue('newVideosBadge').then((isActivated) => {
if (!isActivated) return null

return chrome.browserAction.getBadgeText({}, (currentValueStr) => {
const intValue = decodeValue(currentValueStr)
const newValueStr = encodeValue(intValue + value)
return setBadgeText(newValueStr)
})
return BrowserExtension.browserAction.getBadgeText(
{},
(currentValueStr) => {
const intValue = decodeValue(currentValueStr)
const newValueStr = encodeValue(intValue + value)
return setBadgeText(newValueStr)
}
)
})
}
}
Expand All @@ -41,7 +45,7 @@ export default class BrowserIconBadgeCounter {
* @param {string} text : a 0-4 characters string of the text to display
*/
function setBadgeText(text) {
return chrome.browserAction.setBadgeText({ text })
return BrowserExtension.browserAction.setBadgeText({ text })
}

/**
Expand Down
Loading