Skip to content

Commit

Permalink
Merge branch 'release/2.4.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
electerious committed Nov 15, 2020
2 parents 5e0208d + f268cdb commit b7d5caf
Show file tree
Hide file tree
Showing 32 changed files with 2,093 additions and 305 deletions.
19 changes: 15 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,20 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [2.4.0] - 2020-11-15

Ackee now ignores your own visits once you have logged into the dashboard. Make sure to enable the [`ignoreOwnVisits` option in ackee-tracker](https://github.com/electerious/ackee-tracker#-options) to use this feature. It's currently opt-in, because it requires [a new `Access-Control-Allow-Credentials` header](docs/CORS%20headers.md#credentials), which wasn't previously required. It will be turned on by default in the next major release of Ackee.

> ℹ️ Some browsers strictly block third-party cookies when Ackee runs on a different domain than the site you're visiting. Therefore, it may happen that your own visits still find their way into your statistics, even when the option `ignoreOwnVisits` is turned on.
### Added

- Ignore own visits (#100, thanks @yehudab)
- Tons of new tests (#171, thanks @yehudab)

## [2.3.0] - 2020-11-04

This release adds [support for Vercel](https://github.com/electerious/Ackee/blob/master/docs/Get%20started.md) and updates the included `ackee-tracker` which now ignores bots.
This release adds [support for Vercel](docs/Get%20started.md) and updates the included `ackee-tracker` which now ignores bots.

### Added

Expand All @@ -15,7 +26,7 @@ This release adds [support for Vercel](https://github.com/electerious/Ackee/blob

### Changed

- `ackee-tracker` updated to version 4.1.0
- ackee-tracker updated to version 4.1.0

## [2.2.0] - 2020-11-01

Expand All @@ -42,7 +53,7 @@ This release introduces support for serverless functions. You can now deploy Ack
### Added

- Support for serverless functions and Netlify (#155)
- Added "Deploy to Netlify" to the [Get Started](https://github.com/electerious/Ackee/blob/master/docs/Get%20started.md) guide
- Added "Deploy to Netlify" to the [Get Started](docs/Get%20started.md) guide
- Build all static files into `/dist` by running `yarn build`
- Start the server without rebuilding static files using `yarn server`

Expand Down Expand Up @@ -159,7 +170,7 @@ The first major back-end and front-end rewrite of Ackee with new API, dashboard,

### Added

- Ackee can track detailed data ([optional](https://github.com/electerious/ackee-tracker#options)) and now shows more of them in the "Detailed"-menu
- Ackee can track detailed data ([optional](https://github.com/electerious/ackee-tracker#-options)) and now shows more of them in the "Detailed"-menu

## [1.4.3] - 2020-01-12

Expand Down
11 changes: 11 additions & 0 deletions docs/CORS headers.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ When a site wants to send data to a different domain it needs the permissions to
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, PATCH, OPTIONS
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Credentials: true
```

### Origin
Expand Down Expand Up @@ -47,6 +48,16 @@ The `Access-Control-Allow-Headers` header is used in response to a preflight req
Access-Control-Allow-Headers: Content-Type
```

### Credentials

The `Access-Control-Allow-Credentials` header tells the browser to include the `ackee_ignore` cookie in requests even when you're on a different (sub-)domain. This allows Ackee to ignore your own visits when the [`ignoreOwnVisits` option in ackee-tracker](https://github.com/electerious/ackee-tracker#-options) is enabled and when your browser doesn't block third-party cookies.

> ℹ️ Some browsers strictly block third-party cookies when Ackee runs on a different domain than the site you're visiting. Therefore, it may happen that your own visits still find their way into your statistics, even when the option `ignoreOwnVisits` is turned on.
```
Access-Control-Allow-Credentials: true
```

## Heroku or Platforms-As-A-Service configuration

If you are running Ackee on a platform which handles SSL for you, you may want a quick solution for setting CORS headers instead of using a [reverse proxy](SSL%20and%20HTTPS.md).
Expand Down
2 changes: 2 additions & 0 deletions docs/SSL and HTTPS.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ server {
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET, POST, PATCH, OPTIONS" always;
add_header Access-Control-Allow-Headers "Content-Type" always;
add_header Access-Control-Allow-Credentials "true" always;
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Frame-Options deny;
proxy_pass http://localhost:3000;
Expand Down Expand Up @@ -108,6 +109,7 @@ server {
add_header Access-Control-Allow-Origin "$cors_header" always;
add_header Access-Control-Allow-Methods "GET, POST, PATCH, OPTIONS" always;
add_header Access-Control-Allow-Headers "Content-Type" always;
add_header Access-Control-Allow-Credentials "true" always;
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Frame-Options deny;
proxy_pass http://localhost:3000;
Expand Down
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "ackee",
"private": true,
"version": "2.3.0",
"version": "2.4.0",
"authors": [
"Tobias Reich <tobias@electerious.com>"
],
Expand Down Expand Up @@ -34,9 +34,10 @@
"lint": "eslint '{functions,src,test}/**/*.js'"
},
"dependencies": {
"ackee-tracker": "^4.1.0",
"ackee-tracker": "^4.2.0",
"apollo-server-lambda": "^2.19.0",
"apollo-server-micro": "^2.19.0",
"apollo-server-plugin-http-headers": "^0.1.3",
"classnames": "^2.2.6",
"date-fns": "^2.16.1",
"date-fns-tz": "^1.0.10",
Expand All @@ -47,12 +48,11 @@
"graphql-scalars": "^1.4.1",
"graphql-tools": "^7.0.1",
"human-number": "^1.0.6",
"husky": "^4.3.0",
"immer": "^7.0.14",
"is-url": "^1.2.4",
"micro": "^9.3.4",
"microrouter": "^3.1.3",
"mongoose": "^5.10.11",
"mongoose": "^5.10.14",
"node-fetch": "^2.6.1",
"node-schedule": "^1.3.2",
"normalize-url": "^5.0.0",
Expand Down Expand Up @@ -86,7 +86,9 @@
"eslint-plugin-react": "^7.21.5",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-react-native": "^3.9.1",
"husky": "^4.3.0",
"mocked-env": "^1.3.2",
"mongodb-memory-server": "^6.9.2",
"nodemon": "^2.0.6",
"nyc": "^15.1.0",
"test-listen": "^1.1.0"
Expand Down
21 changes: 19 additions & 2 deletions src/resolvers/records.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,17 @@ const polish = (obj) => {

module.exports = {
Mutation: {
createRecord: async (parent, { domainId, input }, { ip, userAgent }) => {
createRecord: async (parent, { domainId, input }, { ip, userAgent, isIgnored }) => {

// Ignore your own visit when logged in
if (isIgnored === true) {
return {
success: true,
payload: {
id: '88888888-8888-8888-8888-888888888888'
}
}
}

const clientId = identifier(ip, userAgent, domainId)
const data = polish({ ...input, clientId, domainId })
Expand Down Expand Up @@ -100,7 +110,14 @@ module.exports = {
}

},
updateRecord: async (parent, { id }) => {
updateRecord: async (parent, { id }, { isIgnored }) => {

// Ignore your own visit when logged in
if (isIgnored === true) {
return {
success: true
}
}

let entry

Expand Down
11 changes: 9 additions & 2 deletions src/resolvers/tokens.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const tokens = require('../database/tokens')
const KnownError = require('../utils/KnownError')
const ignoreCookie = require('../utils/ignoreCookie')

const response = (entry) => ({
id: entry.id,
Expand All @@ -11,7 +12,7 @@ const response = (entry) => ({

module.exports = {
Mutation: {
createToken: async (parent, { input }) => {
createToken: async (parent, { input }, { setCookies }) => {

const { username, password } = input

Expand All @@ -23,16 +24,22 @@ module.exports = {

const entry = await tokens.add()

// Set cookie to avoid reporting your own visits
setCookies.push(ignoreCookie.on)

return {
success: true,
payload: response(entry)
}

},
deleteToken: async (parent, { id }) => {
deleteToken: async (parent, { id }, { setCookies }) => {

await tokens.del(id)

// Remove cookie to report your own visits, again
setCookies.push(ignoreCookie.off)

return {
success: true
}
Expand Down
22 changes: 4 additions & 18 deletions src/server.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
'use strict'

const { ApolloServer } = require('apollo-server-micro')
const { UnsignedIntResolver, UnsignedIntTypeDefinition, DateTimeResolver, DateTimeTypeDefinition } = require('graphql-scalars')
const micro = require('micro')
const { resolve } = require('path')
const { readFile } = require('fs').promises
const { send, createError } = require('micro')
const { router, get, post, put, patch, del } = require('microrouter')
const { ApolloServer } = require('apollo-server-micro')

const KnownError = require('./utils/KnownError')
const signale = require('./utils/signale')
const isDefined = require('./utils/isDefined')
const isDemoMode = require('./utils/isDemoMode')
const isDevelopmentMode = require('./utils/isDevelopmentMode')
const findMatchingOrigin = require('./utils/findMatchingOrigin')
const customTracker = require('./utils/customTracker')
const createApolloServer = require('./utils/createApolloServer')
const { createMicroContext } = require('./utils/createContext')

const index = readFile(resolve(__dirname, '../dist/index.html')).catch(signale.fatal)
Expand Down Expand Up @@ -85,6 +83,7 @@ const attachCorsHeaders = (fn) => async (req, res) => {
res.setHeader('Access-Control-Allow-Origin', matchingOrigin)
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PATCH, OPTIONS')
res.setHeader('Access-Control-Allow-Headers', 'Content-Type')
res.setHeader('Access-Control-Allow-Credentials', 'true')
}

return fn(req, res)
Expand All @@ -99,21 +98,8 @@ const notFound = async (req) => {

}

const apolloServer = new ApolloServer({
introspection: isDemoMode === true || isDevelopmentMode === true,
playground: isDemoMode === true || isDevelopmentMode === true,
debug: isDevelopmentMode === true,
const apolloServer = createApolloServer(ApolloServer, {
formatError: handleGraphError,
typeDefs: [
UnsignedIntTypeDefinition,
DateTimeTypeDefinition,
require('./types')
],
resolvers: {
UnsignedInt: UnsignedIntResolver,
DateTime: DateTimeResolver,
...require('./resolvers')
},
context: createMicroContext
})

Expand Down
19 changes: 2 additions & 17 deletions src/serverless.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
'use strict'

const { ApolloServer } = require('apollo-server-lambda')
const { UnsignedIntResolver, UnsignedIntTypeDefinition, DateTimeResolver, DateTimeTypeDefinition } = require('graphql-scalars')

const connect = require('./utils/connect')
const isDemoMode = require('./utils/isDemoMode')
const isDevelopmentMode = require('./utils/isDevelopmentMode')
const createApolloServer = require('./utils/createApolloServer')
const { createServerlessContext } = require('./utils/createContext')

const allowOrigin = process.env.ACKEE_ALLOW_ORIGIN || ''
Expand All @@ -17,20 +15,7 @@ if (dbUrl == null) {

connect(dbUrl)

const apolloServer = new ApolloServer({
introspection: isDemoMode === true || isDevelopmentMode === true,
playground: isDemoMode === true || isDevelopmentMode === true,
debug: isDevelopmentMode === true,
typeDefs: [
UnsignedIntTypeDefinition,
DateTimeTypeDefinition,
require('./types')
],
resolvers: {
UnsignedInt: UnsignedIntResolver,
DateTime: DateTimeResolver,
...require('./resolvers')
},
const apolloServer = createApolloServer(ApolloServer, {
context: createServerlessContext
})

Expand Down
27 changes: 27 additions & 0 deletions src/utils/createApolloServer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use strict'

const { UnsignedIntResolver, UnsignedIntTypeDefinition, DateTimeResolver, DateTimeTypeDefinition } = require('graphql-scalars')
const httpHeadersPlugin = require('apollo-server-plugin-http-headers')

const isDemoMode = require('./isDemoMode')
const isDevelopmentMode = require('./isDevelopmentMode')

module.exports = (ApolloServer, opts) => new ApolloServer({
introspection: isDemoMode === true || isDevelopmentMode === true,
playground: isDemoMode === true || isDevelopmentMode === true,
debug: isDevelopmentMode === true,
plugins: [
httpHeadersPlugin
],
typeDefs: [
UnsignedIntTypeDefinition,
DateTimeTypeDefinition,
require('../types')
],
resolvers: {
UnsignedInt: UnsignedIntResolver,
DateTime: DateTimeResolver,
...require('../resolvers')
},
...opts
})
8 changes: 7 additions & 1 deletion src/utils/createContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const { getClientIp } = require('request-ip')
const isDemoMode = require('./isDemoMode')
const isAuthenticated = require('./isAuthenticated')
const createDate = require('./createDate')
const ignoreCookie = require('./ignoreCookie')

const createServerlessContext = async (integrationContext) => {
return createContext(integrationContext.event.headers['client-ip'], integrationContext.event.headers)
Expand All @@ -18,9 +19,14 @@ const createContext = async (ip, headers) => {
return {
isDemoMode,
isAuthenticated: await isAuthenticated(headers['authorization']),
isIgnored: ignoreCookie.isSet(headers['cookie']),
dateDetails: createDate(headers['time-zone']),
userAgent: headers['user-agent'],
ip
ip,
// Variables used by apollo-server-plugin-http-headers
// See: https://github.com/b2a3e8/apollo-server-plugin-http-headers
setCookies: [],
setHeaders: []
}
}

Expand Down
25 changes: 25 additions & 0 deletions src/utils/ignoreCookie.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use static'

const COOKIE_NAME = 'ackee_ignore'

module.exports = {
isSet: (cookie = '') => cookie.includes(`${ COOKIE_NAME }=1`),
on: {
name: COOKIE_NAME,
value: '1',
options: {
maxAge: 365 * 24 * 60 * 60,
sameSite: 'none',
secure: true
}
},
off: {
name: COOKIE_NAME,
value: '0',
options: {
maxAge: -1,
sameSite: 'none',
secure: true
}
}
}
Loading

1 comment on commit b7d5caf

@vercel
Copy link

@vercel vercel bot commented on b7d5caf Nov 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.