[deploy] Introduces Runtime class for standardized & other native cleanup + replace podcast GA with ahoy (#10026)

This commit is contained in:
Fernando Valverde 2020-08-28 05:52:04 -06:00 committed by GitHub
parent cd1db855b6
commit 9da71a47c7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 141 additions and 138 deletions

View file

@ -1,3 +1,5 @@
/* global Runtime */
function initializeCommentDropdown() {
const announcer = document.getElementById('article-copy-link-announcer');
@ -7,14 +9,6 @@ function initializeCommentDropdown() {
);
}
function isNativeAndroidDevice() {
return (
navigator.userAgent === 'DEV-Native-android' &&
typeof AndroidBridge !== 'undefined' &&
AndroidBridge !== null
);
}
function removeClass(className) {
return (element) => element.classList.remove(className);
}
@ -47,7 +41,7 @@ function initializeCommentDropdown() {
function copyText() {
const inputValue = document.getElementById('article-copy-link-input').value;
if (isNativeAndroidDevice()) {
if (Runtime.isNativeAndroid('copyToClipboard')) {
AndroidBridge.copyToClipboard(inputValue);
showAnnouncer();
} else if (isClipboardSupported()) {
@ -113,6 +107,13 @@ function initializeCommentDropdown() {
return;
}
// Android native apps have enhanced sharing capabilities for Articles
const articleShowMoreClicked = button.id === 'article-show-more-button';
if (articleShowMoreClicked && Runtime.isNativeAndroid('shareText')) {
AndroidBridge.shareText(location.href);
return;
}
finalizeAbuseReportLink(
dropdownContent.querySelector('.report-abuse-link-wrapper'),
);

View file

@ -22,12 +22,15 @@
* the problem of using a method before it's defined:
*/
/* global ahoy, Runtime */
/* eslint no-use-before-define: 0 */
/* eslint no-param-reassign: 0 */
var audioInitialized = false;
function initializePodcastPlayback() {
var deviceType = 'web';
function getById(name) {
return document.getElementById(name);
}
@ -100,41 +103,6 @@ function initializePodcastPlayback() {
return podcastLiquidTagrecords;
}
function isNativePlayer() {
return isNativeIOS() || isNativeAndroid();
}
function isNativeIOS() {
return (
navigator.userAgent === 'DEV-Native-ios' &&
window &&
window.webkit &&
window.webkit.messageHandlers &&
window.webkit.messageHandlers.podcast
);
}
function isNativeAndroid() {
return (
navigator.userAgent === 'DEV-Native-android' &&
typeof AndroidBridge !== 'undefined' &&
AndroidBridge !== null &&
AndroidBridge.podcastMessage !== undefined
);
}
function sendNativeMessage(message) {
try {
if (isNativeIOS()) {
window.webkit.messageHandlers.podcast.postMessage(message);
} else if (isNativeAndroid()) {
AndroidBridge.podcastMessage(JSON.stringify(message));
}
} catch (err) {
console.log(err.message); // eslint-disable-line no-console
}
}
function saveMediaState(state) {
var currentState = state || currentAudioState();
var newState = newAudioState();
@ -189,8 +157,8 @@ function initializePodcastPlayback() {
}
function loadAudio(audio) {
if (isNativePlayer()) {
sendNativeMessage({
if (Runtime.podcastMessage) {
Runtime.podcastMessage({
action: 'load',
url: audio.querySelector('source').src,
});
@ -244,8 +212,8 @@ function initializePodcastPlayback() {
}
saveMediaState(currentState);
if (isNativePlayer()) {
sendNativeMessage({
if (Runtime.podcastMessage) {
Runtime.podcastMessage({
action: 'rate',
rate: currentState.playbackRate.toString(),
});
@ -285,8 +253,8 @@ function initializePodcastPlayback() {
function playAudio(audio) {
return new Promise(function (resolve, reject) {
var currentState = currentAudioState();
if (isNativePlayer()) {
sendNativeMessage({
if (Runtime.podcastMessage) {
Runtime.podcastMessage({
action: 'play',
url: audio.querySelector('source').src,
seconds: currentState.currentTime.toString(),
@ -319,23 +287,23 @@ function initializePodcastPlayback() {
}
function sendMetadataMessage() {
try {
var metadata = JSON.parse(fetchMetadataString());
sendNativeMessage({
action: 'metadata',
episodeName: metadata.episodeName,
podcastName: metadata.podcastName,
podcastImageUrl: metadata.podcastImageUrl,
});
} catch (e) {
console.log('Unable to load Podcast Episode metadata', e); // eslint-disable-line no-console
if (Runtime.podcastMessage) {
try {
var metadata = JSON.parse(fetchMetadataString());
Runtime.podcastMessage({
action: 'metadata',
episodeName: metadata.episodeName,
podcastName: metadata.podcastName,
podcastImageUrl: metadata.podcastImageUrl,
});
} catch (e) {
console.log('Unable to load Podcast Episode metadata', e); // eslint-disable-line no-console
}
}
}
function startAudioPlayback(audio) {
if (isNativePlayer()) {
sendMetadataMessage();
}
sendMetadataMessage();
playAudio(audio)
.then(function () {
@ -352,8 +320,8 @@ function initializePodcastPlayback() {
}
function pauseAudioPlayback(audio) {
if (isNativePlayer()) {
sendNativeMessage({ action: 'pause' });
if (Runtime.podcastMessage) {
Runtime.podcastMessage({ action: 'pause' });
} else {
audio.pause();
}
@ -367,29 +335,21 @@ function initializePodcastPlayback() {
window.activePodcast = audio.getAttribute('data-podcast');
var currentState = currentAudioState();
var properties = {
episode: window.activeEpisode,
podcast: window.activePodcast,
deviceType: deviceType,
};
if (!currentState.playing) {
ga(
'send',
'event',
'click',
'play podcast',
`${window.activePodcast} ${window.activeEpisode}`,
null,
);
properties.action = 'play';
changeStatusMessage('initializing...');
startAudioPlayback(audio);
} else {
ga(
'send',
'event',
'click',
'pause podcast',
`${window.activePodcast} ${window.activeEpisode}`,
null,
);
properties.action = 'pause';
pauseAudioPlayback(audio);
changeStatusMessage(null);
}
ahoy.track('Podcast Player Streaming', properties);
}
function muteUnmute(audio) {
@ -408,8 +368,8 @@ function initializePodcastPlayback() {
);
currentState.muted = !currentState.muted;
if (isNativePlayer()) {
sendNativeMessage({
if (Runtime.podcastMessage) {
Runtime.podcastMessage({
action: 'muted',
muted: currentState.muted.toString(),
});
@ -422,8 +382,8 @@ function initializePodcastPlayback() {
function updateVolume(e, audio) {
var currentState = currentAudioState();
currentState.volume = e.target.value / 100;
if (isNativePlayer()) {
sendNativeMessage({ action: 'volume', volume: currentState.volume });
if (Runtime.podcastMessage) {
Runtime.podcastMessage({ action: 'volume', volume: currentState.volume });
} else {
audio.volume = currentState.volume;
}
@ -463,8 +423,8 @@ function initializePodcastPlayback() {
var duration = currentState.duration;
currentState.currentTime = duration * percent; // jumps to 29th secs
if (isNativePlayer()) {
sendNativeMessage({
if (Runtime.podcastMessage) {
Runtime.podcastMessage({
action: 'seek',
seconds: currentState.currentTime.toString(),
});
@ -498,8 +458,8 @@ function initializePodcastPlayback() {
getById('audiocontent').innerHTML = '';
stopRotatingActivePodcastIfExist();
saveMediaState(newAudioState());
if (isNativePlayer()) {
sendNativeMessage({ action: 'terminate' });
if (Runtime.podcastMessage) {
Runtime.podcastMessage({ action: 'terminate' });
}
}
@ -540,6 +500,29 @@ function initializePodcastPlayback() {
mutationObserver.observe(getById('audiocontent'), { attributes: true });
}
// When Runtime.podcastMessage is undefined we need to execute web logic
function initRuntime() {
if (Runtime.isNativeIOS('podcast')) {
deviceType = 'iOS';
Runtime.podcastMessage = function (message) {
try {
window.webkit.messageHandlers.podcast.postMessage(message);
} catch (err) {
console.log(err.message); // eslint-disable-line no-console
}
};
} else if (Runtime.isNativeAndroid('podcastMessage')) {
deviceType = 'Android';
Runtime.podcastMessage = function (message) {
try {
AndroidBridge.podcastMessage(JSON.stringify(message));
} catch (err) {
console.log(err.message); // eslint-disable-line no-console
}
};
}
}
function initializeMedia() {
var currentState = currentAudioState();
document.getElementById('audiocontent').innerHTML = currentState.html;
@ -548,7 +531,7 @@ function initializePodcastPlayback() {
audioInitialized = false;
return;
}
if (!isNativeIOS()) {
if (Runtime.podcastMessage) {
audio.currentTime = currentState.currentTime || 0;
}
loadAudio(audio);
@ -574,6 +557,7 @@ function initializePodcastPlayback() {
saveMediaState(currentState);
}
initRuntime();
spinPodcastRecord();
findAndApplyOnclickToRecords();
if (!audioInitialized) {

View file

@ -12,11 +12,9 @@
/* eslint no-use-before-define: 0 */
/* eslint no-param-reassign: 0 */
/* eslint no-useless-escape: 0 */
/* global jwplayer */
/* global ahoy */
/* global jwplayer, ahoy, Runtime */
function initializeVideoPlayback() {
var nativeBridgeMessage;
var currentTime = '0';
var deviceType = 'web';
var lastEvent = '';
@ -25,25 +23,6 @@ function initializeVideoPlayback() {
return document.getElementById(name);
}
function isNativeIOS() {
return (
navigator.userAgent === 'DEV-Native-ios' &&
window &&
window.webkit &&
window.webkit.messageHandlers &&
window.webkit.messageHandlers.video
);
}
function isNativeAndroid() {
return (
navigator.userAgent === 'DEV-Native-android' &&
typeof AndroidBridge !== 'undefined' &&
AndroidBridge !== null &&
AndroidBridge.videoMessage !== undefined
);
}
function getParameterByName(name, url) {
if (!url) url = window.location.href;
name = name.replace(/[\[\]]/g, '\\$&');
@ -138,7 +117,7 @@ function initializeVideoPlayback() {
getById('pause-butt').classList.add('active');
getById('play-butt').classList.remove('active');
nativeBridgeMessage({
Runtime.videoMessage({
action: 'play',
url: metadata.video_source_url,
seconds: currentTime,
@ -174,18 +153,18 @@ function initializeVideoPlayback() {
var seconds = timeToSeconds(getParameterByName('t') || '0');
var metadata = videoMetadata(videoSource);
if (isNativeIOS()) {
if (Runtime.isNativeIOS('video')) {
deviceType = 'iOS';
nativeBridgeMessage = function (message) {
Runtime.videoMessage = function (message) {
try {
window.webkit.messageHandlers.video.postMessage(message);
} catch (err) {
console.log(err.message); // eslint-disable-line no-console
}
};
} else if (isNativeAndroid()) {
} else if (Runtime.isNativeAndroid('videoMessage')) {
deviceType = 'Android';
nativeBridgeMessage = function (message) {
Runtime.videoMessage = function (message) {
try {
AndroidBridge.videoMessage(JSON.stringify(message));
} catch (err) {

View file

@ -0,0 +1,54 @@
/**
* This class helps managing native feature support. Can easily be referenced
* from anywhere in JavaScript with:
*
* if (Runtime.isNativeiOS('video')) { ... }
*
* if (Runtime.isNativeAndroid('podcastMessage')) { ... }
*/
class Runtime {
/**
* Checks the device for iOS (webkit) native feature support
*
* @function isNativeIOS
* @param {string} namespace Specifies support for a specific feature
* (i.e. video, podcast, etc)
* @returns {boolean} true if current environment support native features
*/
static isNativeIOS(namespace = null) {
const nativeCheck =
/DEV-Native-ios|forem-native-ios/i.test(navigator.userAgent) &&
navigator.userAgent === '' &&
window &&
window.webkit &&
window.webkit.messageHandlers;
let namespaceCheck = true;
if (nativeCheck && namespace) {
namespaceCheck = window.webkit.messageHandlers[namespace] != undefined;
}
return nativeCheck && namespaceCheck;
}
/**
* Checks the device for Android native feature support
*
* @function isNativeAndroid
* @param {string} namespace Specifies support for a specific feature
* (i.e. videoMessage, podcastMessage, etc)
* @returns {boolean} true if current environment support native features
*/
static isNativeAndroid(namespace = null) {
const nativeCheck =
/DEV-Native-android|forem-native-android/i.test(navigator.userAgent) &&
AndroidBridge != undefined;
let namespaceCheck = true;
if (nativeCheck && namespace) {
namespaceCheck = AndroidBridge[namespace] != undefined;
}
return nativeCheck && namespaceCheck;
}
}

View file

@ -1,3 +1,5 @@
/* global Runtime */
import { h } from 'preact';
import { useReducer } from 'preact/hooks';
import { generateMainImage } from '../actions';
@ -5,14 +7,6 @@ import { validateFileInputs } from '../../packs/validateFileInputs';
import { ClipboardButton } from './ClipboardButton';
import { Button, Spinner } from '@crayons';
function isNativeAndroid() {
return (
navigator.userAgent === 'DEV-Native-android' &&
typeof AndroidBridge !== 'undefined' &&
AndroidBridge !== null
);
}
function isClipboardSupported() {
return (
typeof navigator.clipboard !== 'undefined' && navigator.clipboard !== null
@ -108,7 +102,7 @@ export const ImageUploader = () => {
'image-markdown-copy-link-input',
);
if (isNativeAndroid()) {
if (Runtime.isNativeAndroid('copyToClipboard')) {
AndroidBridge.copyToClipboard(imageMarkdownInput.value);
dispatch({
type: 'show_copied_image_message',

View file

@ -1,6 +1,7 @@
/* global Runtime */
import { h, Component } from 'preact';
import PropTypes from 'prop-types';
import { isNativeAndroid } from '../../../utilities/validateAndroidNative';
import { Button } from '@crayons';
@ -46,7 +47,7 @@ export default class InvitationLinkManager extends Component {
'chat-channel-unviation-url',
);
if (isNativeAndroid()) {
if (Runtime.isNativeAndroid('copyToClipboard')) {
AndroidBridge.copyToClipboard(this.imageMarkdownInput.value);
this.setState({ showImageCopiedMessage: true });
} else if (isClipboardSupported()) {

View file

@ -1,10 +0,0 @@
/**
* This function is used to check that is Device is android native or not
*/
export function isNativeAndroid() {
return (
navigator.userAgent === 'DEV-Native-android' &&
typeof AndroidBridge !== 'undefined' &&
AndroidBridge !== null
);
}