diff --git a/docker-compose.yml b/docker-compose.yml
index cf1dbe4..8c08586 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,30 +1,6 @@
version: "3.7"
services:
- aspnet-core:
- build:
- context: .
- dockerfile: mx-platform-aspnet-core/Dockerfile
- entrypoint: sh -c "dotnet mx-platform-aspnet-core.dll"
- environment:
- - ASPNETCORE_ENVIRONMENT=development
- image: mx-quickstart-aspnet
- ports:
- - 8000:80
- node:
- image: mx-quickstart-node
- build:
- context: .
- dockerfile: ./mx-platform-node/Dockerfile
- ports:
- - 8000:8000
- python:
- image: mx-quickstart-python
- build:
- context: .
- dockerfile: ./python/Dockerfile
- ports:
- - 8000:8000
ruby:
image: mx-quickstart-ruby
build:
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index f011ce3..ca404b6 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -18,6 +18,7 @@
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react": "^12.1.4",
"@testing-library/user-event": "^13.5.0",
+ "posthog-js": "^1.33.0",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"react-scripts": "5.0.0",
@@ -3019,6 +3020,14 @@
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.1.1.tgz",
"integrity": "sha512-BUyKJGdDWqvWC5GEhyOiUrGNi9iJUr4CU0O2WxJL6QJhHeeA/NVBalH+FeK0r/x/W0rPymXt5s78TDS7d6lCwg=="
},
+ "node_modules/@sentry/types": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.16.0.tgz",
+ "integrity": "sha512-i6D+OK6d0l/k+VQvRp/Pt21WkDEgVBUIZq+sOkEZJczbcfexVdXKeXXoYTD2vYuFq8Yy28fzlsZaKI+NoH94yQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/@sinonjs/commons": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz",
@@ -7534,6 +7543,11 @@
"bser": "2.1.1"
}
},
+ "node_modules/fflate": {
+ "version": "0.4.8",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz",
+ "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA=="
+ },
"node_modules/file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -13205,6 +13219,16 @@
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
},
+ "node_modules/posthog-js": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.33.0.tgz",
+ "integrity": "sha512-6tCkz7PXfOT8sGoioje29CxyeQmu9SL8JVnjq21rCwU+zRa6Cn48pgcyqSX0Q32WOibdIOT7VoR7ilPv6D/KXg==",
+ "dependencies": {
+ "@sentry/types": "^7.2.0",
+ "fflate": "^0.4.1",
+ "rrweb-snapshot": "^1.1.14"
+ }
+ },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -14182,6 +14206,11 @@
"node": ">=8"
}
},
+ "node_modules/rrweb-snapshot": {
+ "version": "1.1.14",
+ "resolved": "https://registry.npmjs.org/rrweb-snapshot/-/rrweb-snapshot-1.1.14.tgz",
+ "integrity": "sha512-eP5pirNjP5+GewQfcOQY4uBiDnpqxNRc65yKPW0eSoU1XamDfc4M8oqpXGMyUyvLyxFDB0q0+DChuxxiU2FXBQ=="
+ },
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -18598,6 +18627,11 @@
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.1.1.tgz",
"integrity": "sha512-BUyKJGdDWqvWC5GEhyOiUrGNi9iJUr4CU0O2WxJL6QJhHeeA/NVBalH+FeK0r/x/W0rPymXt5s78TDS7d6lCwg=="
},
+ "@sentry/types": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.16.0.tgz",
+ "integrity": "sha512-i6D+OK6d0l/k+VQvRp/Pt21WkDEgVBUIZq+sOkEZJczbcfexVdXKeXXoYTD2vYuFq8Yy28fzlsZaKI+NoH94yQ=="
+ },
"@sinonjs/commons": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz",
@@ -21951,6 +21985,11 @@
"bser": "2.1.1"
}
},
+ "fflate": {
+ "version": "0.4.8",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz",
+ "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA=="
+ },
"file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -25883,6 +25922,16 @@
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
},
+ "posthog-js": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.33.0.tgz",
+ "integrity": "sha512-6tCkz7PXfOT8sGoioje29CxyeQmu9SL8JVnjq21rCwU+zRa6Cn48pgcyqSX0Q32WOibdIOT7VoR7ilPv6D/KXg==",
+ "requires": {
+ "@sentry/types": "^7.2.0",
+ "fflate": "^0.4.1",
+ "rrweb-snapshot": "^1.1.14"
+ }
+ },
"prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -26596,6 +26645,11 @@
}
}
},
+ "rrweb-snapshot": {
+ "version": "1.1.14",
+ "resolved": "https://registry.npmjs.org/rrweb-snapshot/-/rrweb-snapshot-1.1.14.tgz",
+ "integrity": "sha512-eP5pirNjP5+GewQfcOQY4uBiDnpqxNRc65yKPW0eSoU1XamDfc4M8oqpXGMyUyvLyxFDB0q0+DChuxxiU2FXBQ=="
+ },
"run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index c0bbe4f..990cf6d 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -2,8 +2,8 @@
"name": "frontend",
"version": "0.1.0",
"private": true,
- "engines" : {
- "node" : "^16.0.0"
+ "engines": {
+ "node": "^16.0.0"
},
"dependencies": {
"@kyper/button": "^3.1.0",
@@ -16,6 +16,7 @@
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react": "^12.1.4",
"@testing-library/user-event": "^13.5.0",
+ "posthog-js": "^1.33.0",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"react-scripts": "5.0.0",
diff --git a/frontend/src/App.js b/frontend/src/App.js
index 4e1c699..78f11fd 100644
--- a/frontend/src/App.js
+++ b/frontend/src/App.js
@@ -1,19 +1,23 @@
-import { useState } from 'react';
-
import LaunchButton from "./components/LaunchButton";
import UserEndpoints from "./components/UserEndpoints";
+import posthog from 'posthog-js';
+import { useEffect, useState } from 'react';
function App() {
const [userGuid, setUserGuid] = useState(null);
const [memberGuid, setMemberGuid] = useState(null);
+ useEffect(() => {
+ posthog.init(process.env.REACT_APP_POST_HOG_API_KEY, { api_host: 'https://app.posthog.com', autocapture: true })
+ }, [])
+
return (
{userGuid === null && memberGuid === null ? (
-
+
) :
(
diff --git a/frontend/src/components/LaunchButton.js b/frontend/src/components/LaunchButton.js
index de368f4..92369e3 100644
--- a/frontend/src/components/LaunchButton.js
+++ b/frontend/src/components/LaunchButton.js
@@ -9,7 +9,7 @@ import { Dots } from '@kyper/progressindicators';
import { Text } from '@kyper/text'
import { Trash } from '@kyper/icon/Trash'
-function LaunchButton({ setUserGuid, setMemberGuid }) {
+function LaunchButton({ setUserGuid, setMemberGuid, posthog }) {
const [connectWidgetUrl, setConnectWidgetUrl] = useState("");
const [errorMessage, setErrorMessage] = useState(null);
const [isLoading, setIsLoading] = useState(false);
@@ -21,7 +21,7 @@ function LaunchButton({ setUserGuid, setMemberGuid }) {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
};
- await fetch(`/api/users`, requestOptions)
+ await fetch('https://api-59jd.onrender.com'+`/api/users`, requestOptions)
.then(res => {
if (res.ok) {
return res.json();
@@ -46,7 +46,7 @@ function LaunchButton({ setUserGuid, setMemberGuid }) {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
};
- await fetch(`/api/user/${userGuid}`, requestOptions)
+ await fetch('https://api-59jd.onrender.com'+`/api/user/${userGuid}`, requestOptions)
.then(res => {
if (res.ok) {
return res.json();
@@ -76,7 +76,7 @@ function LaunchButton({ setUserGuid, setMemberGuid }) {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
};
- await fetch(`/api/get_mxconnect_widget_url`, requestOptions)
+ await fetch('https://api-59jd.onrender.com'+`/api/get_mxconnect_widget_url`, requestOptions)
.then(res => {
if (res.ok) {
return res.json();
@@ -218,6 +218,14 @@ function LaunchButton({ setUserGuid, setMemberGuid }) {
widgetUrl={connectWidgetUrl}
onEvent={(event) => {
console.log('MX PostMessage: ', event)
+ const user_guid = event.metadata.user_guid;
+ if (user_guid) {
+ console.log('identifying user on posthog');
+ posthog.identify(user_guid);
+ }
+
+ posthog.capture(`Widget Event: ${event.type}`);
+
if (event.type === 'mx/connect/memberConnected') {
setUserGuid(event.metadata.user_guid)
setMemberGuid(event.metadata.member_guid)
diff --git a/frontend/src/components/RunJobAndPoll.js b/frontend/src/components/RunJobAndPoll.js
index 6a82f53..9656184 100644
--- a/frontend/src/components/RunJobAndPoll.js
+++ b/frontend/src/components/RunJobAndPoll.js
@@ -25,7 +25,7 @@ function RunJobAndPoll({
const [connectWidgetUrl, setConnectWidgetUrl] = useState("");
const pollMemberStatus = async () => {
- await fetch(`/users/${userGuid}/members/${memberGuid}/status`, { signal })
+ await fetch('https://api-59jd.onrender.com'+`/users/${userGuid}/members/${memberGuid}/status`, { signal })
.then(response => response.json())
.then((response) => {
console.log('poll member status', response);
@@ -51,7 +51,7 @@ function RunJobAndPoll({
useEffect(() => {
async function initiatePremiumJob() {
console.log(`post request to ${jobType}`)
- await fetch(endpoint, {
+ await fetch('https://api-59jd.onrender.com'+endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -99,7 +99,7 @@ function RunJobAndPoll({
};
console.log('open challenged widget')
async function getWidgetUrl() {
- await fetch(`/api/get_mxconnect_widget_url`, requestOptions)
+ await fetch('https://api-59jd.onrender.com'+`/api/get_mxconnect_widget_url`, requestOptions)
.then(res => res.json())
.then((res) => {
setConnectWidgetUrl(res?.widget_url?.url)
@@ -113,7 +113,7 @@ function RunJobAndPoll({
}, [isChallenged, userGuid, memberGuid])
const getFinalData = async () => {
- await fetch(endpoint)
+ await fetch('https://api-59jd.onrender.com'+endpoint)
.then(response => response.json())
.then((response) => {
console.log('response in final', response);
diff --git a/frontend/src/components/Transactions.js b/frontend/src/components/Transactions.js
index 9362af7..8528539 100644
--- a/frontend/src/components/Transactions.js
+++ b/frontend/src/components/Transactions.js
@@ -10,7 +10,7 @@ function Transactions({userGuid, memberGuid}) {
const loadTransactions = async () => {
setIsLoading(true);
- await fetch(`/users/${userGuid}/members/${memberGuid}/transactions`)
+ await fetch('https://api-59jd.onrender.com'+`/users/${userGuid}/members/${memberGuid}/transactions`)
.then(res => {
if (res.ok) {
return res.json();
diff --git a/frontend/src/components/Verification.js b/frontend/src/components/Verification.js
index 76c2ddd..a3c15a1 100644
--- a/frontend/src/components/Verification.js
+++ b/frontend/src/components/Verification.js
@@ -10,7 +10,7 @@ function Verification({userGuid, memberGuid}) {
const loadAccountNumbers = async () => {
setIsLoading(true);
- await fetch(`/users/${userGuid}/members/${memberGuid}/verify`)
+ await fetch('https://api-59jd.onrender.com'+`/users/${userGuid}/members/${memberGuid}/verify`)
.then(res => {
if (res.ok) {
return res.json();
diff --git a/render.yaml b/render.yaml
new file mode 100644
index 0000000..7fccb1e
--- /dev/null
+++ b/render.yaml
@@ -0,0 +1,38 @@
+services:
+ - type: web
+ plan: free
+ name: api
+ env: ruby
+ repo: https://github.com/mxenabled/mx-quickconnect
+ branch: posthog
+ rootDir: ruby
+ scaling:
+ minInstances: 1
+ maxInstances: 3
+ targetMemoryPercent: 60 # optional if targetCPUPercent is set
+ targetCPUPercent: 60 # optional if targetMemory is set
+ buildCommand: bundle install
+ startCommand: bundle exec ruby app.rb
+ domains:
+ - test0.render.com
+ envVars:
+ - key: STRIPE_API_KEY
+ value: dummy
+ - type: web
+ name: frontend
+ rootDir: frontend
+ repo: https://github.com/mxenabled/mx-quickconnect
+ branch: posthog
+ env: static
+ buildCommand: yarn build
+ staticPublishPath: ./build
+ pullRequestPreviewsEnabled: true # optional
+ buildFilter:
+ paths:
+ - src/**/*.js
+ ignoredPaths:
+ - src/**/*.test.js
+ headers:
+ - path: /*
+ name: X-Frame-Options
+ value: sameorigin
diff --git a/ruby/Gemfile b/ruby/Gemfile
index 384049d..525a01f 100644
--- a/ruby/Gemfile
+++ b/ruby/Gemfile
@@ -9,6 +9,8 @@ gem 'dotenv', '~> 2.7'
gem 'mx-platform-ruby'
+gem 'posthog-ruby'
+
group :test do
# Test it with RSpec (BDD for Ruby)
gem 'rspec-core'
diff --git a/ruby/Gemfile.lock b/ruby/Gemfile.lock
index 4258d3e..6651bdb 100644
--- a/ruby/Gemfile.lock
+++ b/ruby/Gemfile.lock
@@ -2,6 +2,7 @@ GEM
remote: https://rubygems.org/
specs:
colorize (0.8.1)
+ concurrent-ruby (1.1.9)
dotenv (2.7.6)
faraday (1.10.0)
faraday-em_http (~> 1.0)
@@ -38,6 +39,8 @@ GEM
ruby2_keywords (~> 0.0.1)
mx-platform-ruby (0.13.1)
faraday (~> 1.0, >= 1.0.1)
+ posthog-ruby (2.0.0)
+ concurrent-ruby (~> 1, < 1.1.10)
rack (2.2.3.1)
rack-protection (2.2.0)
rack
@@ -56,12 +59,14 @@ GEM
PLATFORMS
x86_64-darwin-19
x86_64-darwin-20
+ x86_64-linux
DEPENDENCIES
colorize (~> 0.8.1)
dotenv (~> 2.7)
httparty (~> 0.20.0)
mx-platform-ruby
+ posthog-ruby
rack (>= 2.2.3.1)
rspec-core
sinatra
diff --git a/ruby/app.rb b/ruby/app.rb
index 940635d..c5f74c1 100644
--- a/ruby/app.rb
+++ b/ruby/app.rb
@@ -8,9 +8,10 @@
require 'base64'
require 'date'
require 'json'
+require 'mx-platform-ruby'
+require 'posthog'
require 'sinatra'
require 'sinatra/cross_origin'
-require 'mx-platform-ruby'
set :port, ENV['APP_PORT'] || 8000
@@ -38,6 +39,12 @@
api_client.default_headers['Accept'] = 'application/vnd.mx.api.v1+json'
mx_platform_api = ::MxPlatformRuby::MxPlatformApi.new(api_client)
+posthog = PostHog::Client.new({
+ api_key: ENV['POST_HOG_API_KEY'],
+ host: ENV['POST_HOG_HOST'], # You can remove this line if you're using https://app.posthog.com
+ on_error: proc { |_status, msg| print msg }
+ })
+
# Checks the env file and production config if in production mode
test_config(mx_platform_api)
@@ -69,13 +76,11 @@ def create_user(user_id, mx_platform_api)
end
delete '/api/user/:guid' do
- begin
- mx_platform_api.delete_user(params[:guid])
- { :user_guid => params[:guid] }.to_json
- rescue ::MxPlatformRuby::ApiError => e
- puts "Error when calling MxPlatformApi->delete_user: #{e.message}"
- [400, e.response_body]
- end
+ mx_platform_api.delete_user(params[:guid])
+ { user_guid: params[:guid] }.to_json
+rescue ::MxPlatformRuby::ApiError => e
+ puts "Error when calling MxPlatformApi->delete_user: #{e.message}"
+ [400, e.response_body]
end
post '/api/get_mxconnect_widget_url' do
@@ -98,6 +103,15 @@ def create_user(user_id, mx_platform_api)
current_member_guid: data['current_member_guid']
)
)
+ posthog.capture(
+ {
+ distinct_id: user_guid,
+ event: 'widget_request_api',
+ properties: {
+ request_body: request_body
+ }
+ }
+ )
response = mx_platform_api.request_widget_url(user_guid, request_body)
response.to_hash.to_json
rescue ::MxPlatformRuby::ApiError => e
@@ -111,12 +125,23 @@ def create_user(user_id, mx_platform_api)
get '/users/:user_guid/members/:member_guid/verify' do
content_type :json
begin
+ posthog.capture({
+ distinct_id: params[:user_guid],
+ event: 'begin verify job'
+ })
# if widget was not in verification mode
# mx_platform_api.verify_member(member_guid, user_guid)
# poll member status answer MFAs
response = mx_platform_api.list_account_numbers_by_member(params[:member_guid], params[:user_guid])
response.to_hash.to_json
rescue ::MxPlatformRuby::ApiError => e
+ posthog.capture({
+ distinct_id: params[:user_guid],
+ event: 'verify failed',
+ properties: {
+ error_message: "Error when calling MxPlatformApi->list_account_numbers_by_member: #{e.message}"
+ }
+ })
puts "Error when calling MxPlatformApi->list_account_numbers_by_member: #{e.message}"
[400, e.response_body]
end
@@ -125,6 +150,10 @@ def create_user(user_id, mx_platform_api)
post '/users/:user_guid/members/:member_guid/identify' do
content_type :json
begin
+ posthog.capture({
+ distinct_id: params[:user_guid],
+ event: 'begin identify job'
+ })
response = mx_platform_api.identify_member(
params[:member_guid],
params[:user_guid]
@@ -143,6 +172,10 @@ def create_user(user_id, mx_platform_api)
params[:member_guid],
params[:user_guid]
)
+ posthog.capture({
+ distinct_id: params[:user_guid],
+ event: 'finish identify job', properties: { response: response.to_hash }
+ })
response.to_hash.to_json
rescue ::MxPlatformRuby::ApiError => e
puts "Error when calling MxPlatformApi->list_account_owners_by_member: #{e.message}"
@@ -154,6 +187,10 @@ def create_user(user_id, mx_platform_api)
content_type :json
begin
response = mx_platform_api.list_user_accounts(params[:user_guid])
+ posthog.capture({
+ distinct_id: params[:user_guid],
+ event: 'finish check_balance job', properties: { response: response.to_hash }
+ })
response.to_hash.to_json
rescue ::MxPlatformRuby::ApiError => e
puts "Error when calling MxPlatformApi->list_user_accounts: #{e.message}"
@@ -164,6 +201,10 @@ def create_user(user_id, mx_platform_api)
post '/users/:user_guid/members/:member_guid/check_balance' do
content_type :json
begin
+ posthog.capture({
+ distinct_id: params[:user_guid],
+ event: 'begin check_balance job'
+ })
response = mx_platform_api.check_balances(
params[:member_guid],
params[:user_guid]
@@ -182,6 +223,10 @@ def create_user(user_id, mx_platform_api)
params[:member_guid],
params[:user_guid]
)
+ posthog.capture({
+ distinct_id: params[:user_guid],
+ event: 'getting transactions', properties: { response: response.to_hash }
+ })
response.to_hash.to_json
rescue ::MxPlatformRuby::ApiError => e
puts "Error when calling MxPlatformApi->list_transactions_by_member: #{e.message}"
@@ -196,6 +241,10 @@ def create_user(user_id, mx_platform_api)
params[:member_guid],
params[:user_guid]
)
+ posthog.capture({
+ distinct_id: params[:user_guid],
+ event: 'getting member status', properties: { response: response.to_hash }
+ })
response.to_hash.to_json
rescue ::MxPlatformRuby::ApiError => e
puts "Error when calling MxPlatformApi->read_member_status: #{e.message}"