Skip to content

Commit

Permalink
Merge pull request #193 from sharetribe/update-v8.6.0-from-upstream
Browse files Browse the repository at this point in the history
Update v8.6.0 from upstream
  • Loading branch information
Gnito authored May 19, 2022
2 parents 1689e03 + db63ecd commit d7debaf
Show file tree
Hide file tree
Showing 15 changed files with 728 additions and 340 deletions.
8 changes: 4 additions & 4 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: 2
jobs:
format:
docker:
- image: circleci/node:12.19
- image: circleci/node:12.20
steps:
- checkout
- restore_cache:
Expand All @@ -25,7 +25,7 @@ jobs:
command: yarn run format-ci
test:
docker:
- image: circleci/node:12.19
- image: circleci/node:12.20
steps:
- checkout
- restore_cache:
Expand All @@ -48,7 +48,7 @@ jobs:
command: yarn run test-ci
build:
docker:
- image: circleci/node:12.19
- image: circleci/node:12.20
steps:
- checkout
- restore_cache:
Expand All @@ -71,7 +71,7 @@ jobs:
command: yarn run build
audit:
docker:
- image: circleci/node:12.19
- image: circleci/node:12.20
steps:
- checkout
- restore_cache:
Expand Down
4 changes: 4 additions & 0 deletions .env-template
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ REACT_APP_CSP=report
# REACT_APP_SENTRY_DSN=change-me
# BASIC_AUTH_USERNAME=sharetribe
# BASIC_AUTH_PASSWORD=secret

# This is GA4 id, which should start with 'G-' prefix.
# You should also turn "Enhanced measurements" off from GA.
# https://support.google.com/analytics/answer/9216061
# REACT_APP_GOOGLE_ANALYTICS_ID=change-me


Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,21 @@ https://github.com/sharetribe/flex-template-web/

## Upcoming version 2022-XX-XX

## [v10.6.0] 2022-05-19

### Updates from upstream (FTW-daily v8.6.0)

- [change] Google Analytics: remove Universal Analytics and start supporting GA4.

NOTE: you need to update the Google Analytics id to GA4's id (starting with 'G-' prefix).

[#1508](https://github.com/sharetribe/ftw-daily/pull/1508)

- [change] Update some outdated dependencies.
[#1514](https://github.com/sharetribe/ftw-daily/pull/1514)

[v10.6.0]: https://github.com/sharetribe/ftw-hourly/compare/v10.5.0...v10.6.0

## [v10.5.0] 2022-05-16

- Add support for hosted translations. (PR made in upstream repo: FTW-daily)
Expand Down
50 changes: 24 additions & 26 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,62 +1,60 @@
{
"name": "app",
"version": "10.5.0",
"version": "10.6.0",
"private": true,
"license": "Apache-2.0",
"dependencies": {
"@babel/runtime": "^7.16.3",
"@formatjs/intl-relativetimeformat": "^9.3.2",
"@babel/runtime": "^7.17.9",
"@loadable/component": "^5.14.1",
"@loadable/server": "^5.14.2",
"@mapbox/polyline": "^1.1.1",
"@sentry/browser": "^6.15.0",
"@sentry/node": "^6.15.0",
"array-includes": "^3.1.4",
"array.prototype.find": "^2.1.2",
"@sentry/browser": "^6.19.7",
"@sentry/node": "^6.19.7",
"array-includes": "^3.1.5",
"array.prototype.find": "^2.2.0",
"autosize": "^5.0.1",
"basic-auth": "^2.0.1",
"body-parser": "^1.18.3",
"body-parser": "^1.20.0",
"classnames": "^2.3.1",
"compression": "^1.7.4",
"cookie-parser": "^1.4.6",
"core-js": "^3.19.2",
"core-js": "^3.22.5",
"cors": "^2.8.5",
"decimal.js": "^10.3.1",
"dotenv": "8.2.0",
"dotenv-expand": "5.1.0",
"express": "^4.16.4",
"express": "^4.18.1",
"express-enforces-ssl": "^1.1.0",
"express-sitemap": "^1.8.0",
"final-form": "^4.20.4",
"final-form": "^4.20.7",
"final-form-arrays": "^3.0.2",
"full-icu": "^1.4.0",
"helmet": "^4.6.0",
"intl-pluralrules": "^1.3.1",
"jose": "3.11.4",
"jose": "4.8.1",
"jstimezonedetect": "^1.0.7",
"lodash": "^4.17.21",
"mapbox-gl-multitouch": "^1.0.3",
"moment": "^2.29.1",
"moment": "^2.29.3",
"moment-timezone": "^0.5.34",
"object.entries": "^1.1.5",
"object.values": "^1.1.5",
"passport": "^0.5.0",
"passport": "^0.5.3",
"passport-facebook": "^3.0.0",
"passport-google-oauth": "^2.0.0",
"path-to-regexp": "^6.2.0",
"prop-types": "^15.7.2",
"query-string": "^7.0.1",
"path-to-regexp": "^6.2.1",
"prop-types": "^15.8.1",
"query-string": "^7.1.1",
"raf": "^3.4.0",
"react": "^16.13.1",
"react-dates": "^21.8.0",
"react-dom": "^16.13.1",
"react-final-form": "^6.5.7",
"react-final-form": "^6.5.9",
"react-final-form-arrays": "^3.1.3",
"react-helmet-async": "^1.1.2",
"react-intl": "^5.22.0",
"react-helmet-async": "^1.3.0",
"react-intl": "^5.25.1",
"react-moment-proptypes": "^1.8.1",
"react-redux": "^7.2.6",
"react-router-dom": "^5.2.0",
"react-redux": "^7.2.8",
"react-router-dom": "^5.3.2",
"react-with-direction": "^1.4.0",
"redux": "^4.1.2",
"redux-thunk": "^2.4.1",
Expand All @@ -70,12 +68,12 @@
"devDependencies": {
"bfj": "^7.0.2",
"chalk": "^v4.1.2",
"concurrently": "^6.4.0",
"concurrently": "^7.2.0",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.6",
"enzyme-to-json": "^3.6.2",
"inquirer": "^8.2.0",
"nodemon": "^2.0.15",
"inquirer": "^8.2.4",
"nodemon": "^2.0.16",
"prettier": "^1.18.2"
},
"resolutions": {
Expand Down
7 changes: 3 additions & 4 deletions server/api-util/idToken.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const crypto = require('crypto');
const { default: fromKeyLike } = require('jose/jwk/from_key_like');
const { default: SignJWT } = require('jose/jwt/sign');
const jose = require('jose');

const radix = 10;
const PORT = parseInt(process.env.REACT_APP_DEV_API_SERVER_PORT, radix);
Expand Down Expand Up @@ -50,7 +49,7 @@ exports.createIdToken = (idpClientId, user, options) => {

const { userId, firstName, lastName, email, emailVerified } = user;

const jwt = new SignJWT({
const jwt = new jose.SignJWT({
given_name: firstName,
family_name: lastName,
email: email,
Expand Down Expand Up @@ -89,7 +88,7 @@ exports.openIdConfiguration = (req, res) => {
*/
exports.jwksUri = keys => (req, res) => {
const jwkKeys = keys.map(key => {
return fromKeyLike(crypto.createPublicKey(key.rsaPublicKey)).then(res => {
return jose.exportJWK(crypto.createPublicKey(key.rsaPublicKey)).then(res => {
return { alg: key.alg, kid: key.keyId, ...res };
});
});
Expand Down
3 changes: 3 additions & 0 deletions server/csp.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const defaultDirectives = {
'events.mapbox.com',

// Google Analytics
'​www.​googletagm­anager.​com',
'www.google-analytics.com',
'stats.g.doubleclick.net',

Expand Down Expand Up @@ -59,6 +60,7 @@ const defaultDirectives = {
'*.ggpht.com',

// Google Analytics
'www.googletagmanager.com',
'www.google.com',
'www.google-analytics.com',
'stats.g.doubleclick.net',
Expand All @@ -72,6 +74,7 @@ const defaultDirectives = {
data,
'maps.googleapis.com',
'api.mapbox.com',
'​www.​googletagm­anager.​com',
'*.google-analytics.com',
'js.stripe.com',
],
Expand Down
35 changes: 23 additions & 12 deletions server/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,20 +121,31 @@ exports.render = function(requestUrl, context, data, renderApp, webExtractor) {

// We want to precisely control where the analytics script is
// injected in the HTML file so we can catch all events as early as
// possible. This is why we inject the GA script separately from
// react-helmet. This script also ensures that all the GA scripts
// possible. This script also ensures that all the GA scripts
// are added only when the proper env var is present.
// NOTE: when dealing with cookie consents, it might make more sense to
// include this script through react-helmet.
//
// See: https://developers.google.com/analytics/devguides/collection/analyticsjs/#alternative_async_tracking_snippet
const googleAnalyticsScript = process.env.REACT_APP_GOOGLE_ANALYTICS_ID
? `
<script>
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
ga('create', '${process.env.REACT_APP_GOOGLE_ANALYTICS_ID}', 'auto');
</script>
<script async src="https://www.google-analytics.com/analytics.js"></script>
`
: '';
// See: https://developers.google.com/analytics/devguides/collection/gtagjs
const googleAnalyticsId = process.env.REACT_APP_GOOGLE_ANALYTICS_ID;
// Add Google Analytics script if correct id exists (it should start with 'G-' prefix)
const hasGoogleAnalyticsv4Id = googleAnalyticsId.indexOf('G-') === 0;

// Google Analytics: gtag.js
// NOTE: FTW is a single-page application (SPA).
// gtag.js sends initial page_view event after page load.
// but we need to handle subsequent events for in-app navigation.
const gtagScripts = `
<script async src="https://www.googletagmanager.com/gtag/js?id=${googleAnalyticsId}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${googleAnalyticsId}');
</script>
`;
const googleAnalyticsScript = hasGoogleAnalyticsv4Id ? gtagScripts : '';

return template({
htmlAttributes: head.htmlAttributes.toString(),
Expand Down
4 changes: 2 additions & 2 deletions src/Routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ const setPageScrollPosition = location => {

const handleLocationChanged = (dispatch, location) => {
setPageScrollPosition(location);
const url = canonicalRoutePath(routeConfiguration(), location);
dispatch(locationChanged(location, url));
const path = canonicalRoutePath(routeConfiguration(), location);
dispatch(locationChanged(location, path));
};

/**
Expand Down
9 changes: 5 additions & 4 deletions src/analytics/analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import { LOCATION_CHANGED } from '../ducks/Routing.duck';
// Create a Redux middleware from the given analytics handlers. Each
// handler should have the following methods:
//
// - trackPageView(url): called when the URL is changed
export const createMiddleware = handlers => () => next => action => {
// - trackPageView(canonicalPath, previousPath): called when the URL is changed
export const createMiddleware = handlers => store => next => action => {
const { type, payload } = action;

if (type === LOCATION_CHANGED) {
const { canonicalUrl } = payload;
const previousPath = store?.getState()?.Routing?.currentCanonicalPath;
const { canonicalPath } = payload;
handlers.forEach(handler => {
handler.trackPageView(canonicalUrl);
handler.trackPageView(canonicalPath, previousPath);
});
}

Expand Down
30 changes: 22 additions & 8 deletions src/analytics/handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,30 @@ export class LoggingAnalyticsHandler {
}
}

// Google Analytics 4 (GA4) using gtag.js script, which is included in server/rendered.js
// Note: the script is only available locally when running "yarn run dev-server"
export class GoogleAnalyticsHandler {
constructor(ga) {
if (typeof ga !== 'function') {
throw new Error('Variable `ga` missing for Google Analytics');
constructor(gtag) {
if (typeof gtag !== 'function') {
throw new Error('Variable `gtag` missing for Google Analytics');
}
this.ga = ga;
this.gtag = gtag;
}
trackPageView(url) {
// https://developers.google.com/analytics/devguides/collection/analyticsjs/single-page-applications#tracking_virtual_pageviews
this.ga('set', 'page', url);
this.ga('send', 'pageview');
trackPageView(canonicalPath, previousPath) {
// GA4 property. Manually send page_view events
// https://developers.google.com/analytics/devguides/collection/gtagjs/single-page-applications
// Note 1: You should turn "Enhanced measurement" off.
// It attaches own listeners to elements and that breaks in-app navigation.
// Note 2: If previousPath is null (just after page load), gtag script sends page_view event automatically.
// Only in-app navigation needs to be sent manually from SPA.
// Note 3: Timeout is needed because gtag script picks up <title>,
// and location change event happens before initial rendering.
if (previousPath) {
window.setTimeout(() => {
this.gtag('event', 'page_view', {
page_path: canonicalPath,
});
}, 300);
}
}
}
3 changes: 2 additions & 1 deletion src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ import defaultMessages from './translations/en.json';
// 1) Change the language in the config.js file!
// 2) Import correct locale rules for Moment library
// 3) Use the `messagesInLocale` import to add the correct translation file.
// 4) To support older browsers we need add the correct locale for intl-relativetimeformat to `util/polyfills.js`
// 4) (optionally) To support older browsers you need add the intl-relativetimeformat npm packages
// and take it into use in `util/polyfills.js`

// Note that there is also translations in './translations/countryCodes.js' file
// This file contains ISO 3166-1 alpha-2 country codes, country names and their translations in our default languages
Expand Down
8 changes: 4 additions & 4 deletions src/ducks/Routing.duck.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const LOCATION_CHANGED = 'app/Routing/LOCATION_CHANGED';

const initialState = {
currentLocation: null,
currentCanonicalUrl: null,
currentCanonicalPath: null,
};

export default function routingReducer(state = initialState, action = {}) {
Expand All @@ -16,7 +16,7 @@ export default function routingReducer(state = initialState, action = {}) {
return {
...state,
currentLocation: payload.location,
currentCanonicalUrl: payload.canonicalUrl,
currentCanonicalPath: payload.canonicalPath,
};

default:
Expand All @@ -26,7 +26,7 @@ export default function routingReducer(state = initialState, action = {}) {

// ================ Action creators ================ //

export const locationChanged = (location, canonicalUrl) => ({
export const locationChanged = (location, canonicalPath) => ({
type: LOCATION_CHANGED,
payload: { location, canonicalUrl },
payload: { location, canonicalPath },
});
13 changes: 9 additions & 4 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,19 @@ const setupAnalyticsHandlers = () => {
handlers.push(new LoggingAnalyticsHandler());
}

// Add Google Analytics handler if tracker ID is found
// Add Google Analytics 4 (GA4) handler if tracker ID is found
if (process.env.REACT_APP_GOOGLE_ANALYTICS_ID) {
if (window?.ga) {
handlers.push(new GoogleAnalyticsHandler(window.ga));
if (window?.gtag) {
handlers.push(new GoogleAnalyticsHandler(window.gtag));
} else {
// Some adblockers (e.g. Ghostery) might block the Google Analytics integration.
console.warn(
'Google Analytics (window.ga) is not available. It might be that your adblocker is blocking it.'
'Google Analytics (window.gtag) is not available. It might be that your adblocker is blocking it.'
);
}
if (process.env.REACT_APP_GOOGLE_ANALYTICS_ID.indexOf('G-') !== 0) {
console.warn(
'Google Analytics 4 (GA4) should have measurement id that starts with "G-" prefix'
);
}
}
Expand Down
Loading

0 comments on commit d7debaf

Please sign in to comment.