Compare commits

...

30 Commits

Author SHA1 Message Date
Hailey 2c754bf6e0 Merge branch 'main' into hailey/linking-referrers 2024-07-11 18:38:38 -07:00
Hailey 0753ed099f Merge branch 'hailey/shared-preferences' into hailey/linking-referrers 2024-07-11 18:18:52 -07:00
Hailey caf2f31d9a Merge remote-tracking branch 'origin/main' into hailey/shared-preferences 2024-07-11 18:17:02 -07:00
Hailey 847513e6d2 lint 2024-07-11 18:13:01 -07:00
Hailey cc5f930e0f Merge branch 'hailey/shared-preferences' into hailey/linking-referrers 2024-07-11 18:08:38 -07:00
Hailey f08afb7404 Merge branch 'hailey/swiftlint' into hailey/shared-preferences 2024-07-11 18:04:44 -07:00
Hailey e456fb4a0e Merge remote-tracking branch 'origin/main' into hailey/swiftlint 2024-07-11 18:04:29 -07:00
Hailey f62f9b5ca4 lint 2024-07-11 18:03:05 -07:00
Hailey df82678ebf Merge branch 'hailey/swiftlint' into hailey/shared-preferences 2024-07-11 17:50:22 -07:00
Hailey 209be23586 Merge remote-tracking branch 'origin/main' into hailey/shared-preferences 2024-07-11 17:49:39 -07:00
Hailey 4491b3d15a remove yml 2024-07-10 18:02:03 -07:00
Hailey 2c6ff9bea4 newline 2024-07-10 17:53:59 -07:00
Hailey b37cdb135b lint more 2024-07-10 17:50:27 -07:00
Hailey 19da52f1c7 lint more 2024-07-10 17:49:54 -07:00
Hailey de25c4b676 fix kotlin 2024-07-10 17:41:45 -07:00
Hailey 921b13dbdc fix swift 2024-07-10 17:40:42 -07:00
Hailey 73585cbd17 tweaks 2024-07-01 18:31:50 -07:00
Hailey 665cbcdace Merge branch 'hailey/shared-preferences' into hailey/linking-referrers 2024-07-01 18:20:41 -07:00
Hailey ffe15a981a shared preferences api 2024-07-01 18:10:53 -07:00
Hailey d41fae5456 include to 2024-06-25 14:47:30 -07:00
Hailey 9c337b7b6b update imports 2024-06-25 14:16:46 -07:00
Hailey 2cee8448fb comment 2024-06-25 14:10:57 -07:00
Hailey 1ccafde11b finish migration 2024-06-25 14:09:36 -07:00
Hailey b57217cb4c move ios over to sak 2024-06-25 13:36:04 -07:00
Hailey 792bb29712 Merge branch 'main' into hailey/linking-referrers 2024-06-25 10:22:29 -07:00
Hailey c1c188eef4 clarify var names 2024-06-14 10:54:30 -07:00
Hailey dacc273458 rm log 2024-06-14 10:26:05 -07:00
Hailey b3e618d990 cfg 2024-06-14 00:43:12 -07:00
Hailey fe2abee7e3 rm 2024-06-14 00:42:15 -07:00
Hailey 57de57ce6a referrers for all platforms 2024-06-14 00:40:47 -07:00
12 changed files with 213 additions and 34 deletions
+1
View File
@@ -221,6 +221,7 @@ module.exports = function (config) {
'./plugins/withAndroidSplashScreenStatusBarTranslucentPlugin.js',
'./plugins/shareExtension/withShareExtensions.js',
'./plugins/notificationsExtension/withNotificationsExtension.js',
'./plugins/withAppDelegateReferrer.js',
].filter(Boolean),
extra: {
eas: {
@@ -1,5 +1,8 @@
package expo.modules.blueskyswissarmy.referrer
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.util.Log
import com.android.installreferrer.api.InstallReferrerClient
import com.android.installreferrer.api.InstallReferrerStateListener
@@ -8,10 +11,53 @@ import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
class ExpoBlueskyReferrerModule : Module() {
private var intent: Intent? = null
private var activityReferrer: Uri? = null
override fun definition() =
ModuleDefinition {
Name("ExpoBlueskyReferrer")
OnNewIntent {
intent = it
activityReferrer = appContext.currentActivity?.referrer
}
AsyncFunction("getReferrerInfoAsync") {
val intentReferrer =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent?.getParcelableExtra(Intent.EXTRA_REFERRER, Uri::class.java)
} else {
intent?.getParcelableExtra(Intent.EXTRA_REFERRER)
}
// Some apps explicitly set a referrer, like Chrome. In these cases, we prefer this since
// it's the actual website that the user came from rather than the app.
if (intentReferrer is Uri) {
val res =
mapOf(
"referrer" to intentReferrer.toString(),
"hostname" to intentReferrer.host,
)
intent = null
return@AsyncFunction res
}
// In all other cases, we'll just record the app that sent the intent.
if (activityReferrer != null) {
// referrer could become null here. `.toString()` though can be called on null
val res =
mapOf(
"referrer" to activityReferrer.toString(),
"hostname" to (activityReferrer?.host ?: ""),
)
activityReferrer = null
return@AsyncFunction res
}
return@AsyncFunction null
}
AsyncFunction("getGooglePlayReferrerInfoAsync") { promise: Promise ->
val referrerClient = InstallReferrerClient.newBuilder(appContext.reactContext).build()
referrerClient.startConnection(
@@ -1,9 +1,13 @@
import {requireNativeModule} from 'expo'
import {GooglePlayReferrerInfo} from './types'
import {GooglePlayReferrerInfo, ReferrerInfo} from './types'
export const NativeModule = requireNativeModule('ExpoBlueskyReferrer')
export function getGooglePlayReferrerInfoAsync(): Promise<GooglePlayReferrerInfo> {
export function getGooglePlayReferrerInfoAsync(): Promise<GooglePlayReferrerInfo | null> {
return NativeModule.getGooglePlayReferrerInfoAsync()
}
export function getReferrerInfoAsync(): Promise<ReferrerInfo | null> {
return NativeModule.getReferrerInfoAsync()
}
@@ -0,0 +1,37 @@
import {SharedPrefs} from '../../index'
import {NotImplementedError} from '../NotImplemented'
import {GooglePlayReferrerInfo, ReferrerInfo} from './types'
export function getGooglePlayReferrerInfoAsync(): Promise<GooglePlayReferrerInfo> {
throw new NotImplementedError()
}
export function getReferrerInfoAsync(): Promise<ReferrerInfo | null> {
const referrer = SharedPrefs.getString('referrer')
if (referrer) {
SharedPrefs.removeValue('referrer')
try {
const url = new URL(referrer)
return {
referrer,
hostname: url.hostname,
}
} catch (e) {
return {
referrer,
hostname: undefined,
}
}
}
const referrerApp = SharedPrefs.getString('referrerApp')
if (referrerApp) {
SharedPrefs.removeValue('referrerApp')
return {
referrer: referrerApp,
hostname: referrerApp,
}
}
return null
}
@@ -1,7 +1,10 @@
import {NotImplementedError} from '../NotImplemented'
import {GooglePlayReferrerInfo} from './types'
import {GooglePlayReferrerInfo, ReferrerInfo} from './types'
// @ts-ignore throws
export function getGooglePlayReferrerInfoAsync(): Promise<GooglePlayReferrerInfo> {
throw new NotImplementedError()
}
export function getReferrerInfoAsync(): Promise<ReferrerInfo | null> {
throw new NotImplementedError()
}
@@ -0,0 +1,34 @@
import {Platform} from 'react-native'
import {NotImplementedError} from '../NotImplemented'
import {GooglePlayReferrerInfo, ReferrerInfo} from './types'
export function getGooglePlayReferrerInfoAsync(): Promise<GooglePlayReferrerInfo> {
throw new NotImplementedError()
}
export function getReferrerInfoAsync(): Promise<ReferrerInfo | null> {
if (
Platform.OS === 'web' &&
// for ssr
typeof document !== 'undefined' &&
document != null &&
document.referrer
) {
try {
const url = new URL(document.referrer)
if (url.hostname !== 'bsky.app') {
return {
referrer: url.href,
hostname: url.hostname,
}
}
} catch {
// If something happens to the URL parsing, we don't want to actually cause any problems for the user. Just
// log the error so we might catch it
console.error('Failed to parse referrer URL')
}
}
return null
}
@@ -1,7 +1,10 @@
export type GooglePlayReferrerInfo =
| {
installReferrer?: string
clickTimestamp?: number
installTimestamp?: number
}
| undefined
export type GooglePlayReferrerInfo = {
installReferrer?: string
clickTimestamp?: number
installTimestamp?: number
}
export type ReferrerInfo = {
referrer: string
hostname: string
}
+41
View File
@@ -0,0 +1,41 @@
const {withAppDelegate} = require('@expo/config-plugins')
const {mergeContents} = require('@expo/config-plugins/build/utils/generateCode')
const path = require('path')
const fs = require('fs')
module.exports = config => {
// eslint-disable-next-line no-shadow
return withAppDelegate(config, async config => {
const delegatePath = path.join(
config.modRequest.platformProjectRoot,
'AppDelegate.mm',
)
let newContents = config.modResults.contents
newContents = mergeContents({
src: newContents,
anchor: '// Linking API',
newSrc: `
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:options[UIApplicationOpenURLOptionsSourceApplicationKey] forKey:@"referrerApp"];\n`,
offset: 2,
tag: 'referrer info - deep links',
comment: '//',
}).contents
newContents = mergeContents({
src: newContents,
anchor: '// Universal Links',
newSrc: `
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setURL:userActivity.referrerURL forKey:@"referrer"];\n`,
offset: 2,
tag: 'referrer info - universal links',
comment: '//',
}).contents
config.modResults.contents = newContents
return config
})
}
+14 -1
View File
@@ -31,7 +31,7 @@ import {
} from 'lib/routes/types'
import {RouteParams, State} from 'lib/routes/types'
import {bskyTitle} from 'lib/strings/headings'
import {isAndroid, isNative} from 'platform/detection'
import {isAndroid, isNative, isWeb} from 'platform/detection'
import {PreferencesExternalEmbeds} from '#/view/screens/PreferencesExternalEmbeds'
import {AppPasswords} from 'view/screens/AppPasswords'
import {ModerationBlockedAccounts} from 'view/screens/ModerationBlockedAccounts'
@@ -49,6 +49,7 @@ import {
StarterPackScreenShort,
} from '#/screens/StarterPack/StarterPackScreen'
import {Wizard} from '#/screens/StarterPack/Wizard'
import {Referrer} from '../modules/expo-bluesky-swiss-army'
import {init as initAnalytics} from './lib/analytics/analytics'
import {useWebScrollRestoration} from './lib/hooks/useWebScrollRestoration'
import {attachRouteToLogEvents, logEvent} from './lib/statsig/statsig'
@@ -769,6 +770,18 @@ function logModuleInitTime() {
initMs,
})
if (isWeb) {
Referrer.getReferrerInfoAsync().then(info => {
if (info && info.hostname !== 'bsky.app') {
logEvent('deepLink:referrerReceived', {
to: window.location.href,
referrer: info?.referrer,
hostname: info?.hostname,
})
}
})
}
if (__DEV__) {
// This log is noisy, so keep false committed
const shouldLog = false
+14 -1
View File
@@ -1,9 +1,12 @@
import React from 'react'
import * as Linking from 'expo-linking'
import {logEvent} from 'lib/statsig/statsig'
import {isNative} from 'platform/detection'
import {useComposerControls} from 'state/shell'
import {useSession} from 'state/session'
import {useComposerControls} from 'state/shell'
import {useCloseAllActiveElements} from 'state/util'
import {Referrer} from '../../../modules/expo-bluesky-swiss-army'
type IntentType = 'compose'
@@ -15,6 +18,16 @@ export function useIntentHandler() {
React.useEffect(() => {
const handleIncomingURL = (url: string) => {
Referrer.getReferrerInfoAsync().then(info => {
if (info && info.hostname !== 'bsky.app') {
logEvent('deepLink:referrerReceived', {
to: url,
referrer: info?.referrer,
hostname: info?.hostname,
})
}
})
// We want to be able to support bluesky:// deeplinks. It's unnatural for someone to use a deeplink with three
// slashes, like bluesky:///intent/follow. However, supporting just two slashes causes us to have to take care
// of two cases when parsing the url. If we ensure there is a third slash, we can always ensure the first
+5
View File
@@ -25,6 +25,11 @@ export type LogEvents = {
}
'state:foreground:sampled': {}
'router:navigate:sampled': {}
'deepLink:referrerReceived': {
to: string
referrer: string
hostname: string
}
// Screen events
'splash:signInPressed': {}
-21
View File
@@ -28,8 +28,6 @@ type StatsigUser = {
bundleDate: number
refSrc: string
refUrl: string
referrer: string
referrerHostname: string
appLanguage: string
contentLanguages: string[]
}
@@ -37,29 +35,12 @@ type StatsigUser = {
let refSrc = ''
let refUrl = ''
let referrer = ''
let referrerHostname = ''
if (isWeb && typeof window !== 'undefined') {
const params = new URLSearchParams(window.location.search)
refSrc = params.get('ref_src') ?? ''
refUrl = decodeURIComponent(params.get('ref_url') ?? '')
}
if (
isWeb &&
typeof document !== 'undefined' &&
document != null &&
document.referrer
) {
try {
const url = new URL(document.referrer)
if (url.hostname !== 'bsky.app') {
referrer = document.referrer
referrerHostname = url.hostname
}
} catch {}
}
export type {LogEvents}
function createStatsigOptions(prefetchUsers: StatsigUser[]) {
@@ -222,8 +203,6 @@ function toStatsigUser(did: string | undefined): StatsigUser {
custom: {
refSrc,
refUrl,
referrer,
referrerHostname,
platform: Platform.OS as 'ios' | 'android' | 'web',
bundleIdentifier: BUNDLE_IDENTIFIER,
bundleDate: BUNDLE_DATE,