diff --git a/__tests__/lib/string.test.ts b/__tests__/lib/string.test.ts
index 30072ccb1..0da9551e3 100644
--- a/__tests__/lib/string.test.ts
+++ b/__tests__/lib/string.test.ts
@@ -1,6 +1,11 @@
import {RichText} from '@atproto/api'
import {parseEmbedPlayerFromUrl} from 'lib/strings/embed-player'
+import {
+ createStarterPackGooglePlayUri,
+ createStarterPackLinkFromAndroidReferrer,
+ parseStarterPackUri,
+} from 'lib/strings/starter-pack'
import {cleanError} from '../../src/lib/strings/errors'
import {createFullHandle, makeValidHandle} from '../../src/lib/strings/handles'
import {enforceLen} from '../../src/lib/strings/helpers'
@@ -796,3 +801,179 @@ describe('parseEmbedPlayerFromUrl', () => {
}
})
})
+
+describe('createStarterPackLinkFromAndroidReferrer', () => {
+ const validOutput = 'at://haileyok.com/app.bsky.graph.starterpack/rkey'
+
+ it('returns a link when input contains utm_source and utm_content', () => {
+ expect(
+ createStarterPackLinkFromAndroidReferrer(
+ 'utm_source=bluesky&utm_content=starterpack_haileyok.com_rkey',
+ ),
+ ).toEqual(validOutput)
+
+ expect(
+ createStarterPackLinkFromAndroidReferrer(
+ 'utm_source=bluesky&utm_content=starterpack_test-lover-9000.com_rkey',
+ ),
+ ).toEqual('at://test-lover-9000.com/app.bsky.graph.starterpack/rkey')
+ })
+
+ it('returns a link when input contains utm_source and utm_content in different order', () => {
+ expect(
+ createStarterPackLinkFromAndroidReferrer(
+ 'utm_content=starterpack_haileyok.com_rkey&utm_source=bluesky',
+ ),
+ ).toEqual(validOutput)
+ })
+
+ it('returns a link when input contains other parameters as well', () => {
+ expect(
+ createStarterPackLinkFromAndroidReferrer(
+ 'utm_source=bluesky&utm_medium=starterpack&utm_content=starterpack_haileyok.com_rkey',
+ ),
+ ).toEqual(validOutput)
+ })
+
+ it('returns null when utm_source is not present', () => {
+ expect(
+ createStarterPackLinkFromAndroidReferrer(
+ 'utm_content=starterpack_haileyok.com_rkey',
+ ),
+ ).toEqual(null)
+ })
+
+ it('returns null when utm_content is not present', () => {
+ expect(
+ createStarterPackLinkFromAndroidReferrer('utm_source=bluesky'),
+ ).toEqual(null)
+ })
+
+ it('returns null when utm_content is malformed', () => {
+ expect(
+ createStarterPackLinkFromAndroidReferrer(
+ 'utm_content=starterpack_haileyok.com',
+ ),
+ ).toEqual(null)
+
+ expect(
+ createStarterPackLinkFromAndroidReferrer('utm_content=starterpack'),
+ ).toEqual(null)
+
+ expect(
+ createStarterPackLinkFromAndroidReferrer(
+ 'utm_content=starterpack_haileyok.com_rkey_more',
+ ),
+ ).toEqual(null)
+
+ expect(
+ createStarterPackLinkFromAndroidReferrer(
+ 'utm_content=notastarterpack_haileyok.com_rkey',
+ ),
+ ).toEqual(null)
+ })
+})
+
+describe('parseStarterPackHttpUri', () => {
+ const baseUri = 'https://bsky.app/start'
+
+ it('returns a valid at uri when http uri is valid', () => {
+ const validHttpUri = `${baseUri}/haileyok.com/rkey`
+ expect(parseStarterPackUri(validHttpUri)).toEqual({
+ name: 'haileyok.com',
+ rkey: 'rkey',
+ })
+
+ const validHttpUri2 = `${baseUri}/haileyok.com/ilovetesting`
+ expect(parseStarterPackUri(validHttpUri2)).toEqual({
+ name: 'haileyok.com',
+ rkey: 'ilovetesting',
+ })
+
+ const validHttpUri3 = `${baseUri}/testlover9000.com/rkey`
+ expect(parseStarterPackUri(validHttpUri3)).toEqual({
+ name: 'testlover9000.com',
+ rkey: 'rkey',
+ })
+ })
+
+ it('returns null when there is no rkey', () => {
+ const validHttpUri = `${baseUri}/haileyok.com`
+ expect(parseStarterPackUri(validHttpUri)).toEqual(null)
+ })
+
+ it('returns null when there is an extra path', () => {
+ const validHttpUri = `${baseUri}/haileyok.com/rkey/other`
+ expect(parseStarterPackUri(validHttpUri)).toEqual(null)
+ })
+
+ it('returns null when there is no handle or rkey', () => {
+ const validHttpUri = `${baseUri}`
+ expect(parseStarterPackUri(validHttpUri)).toEqual(null)
+ })
+
+ it('returns null when the route is not /start or /starter-pack', () => {
+ const validHttpUri = 'https://bsky.app/start/haileyok.com/rkey'
+ expect(parseStarterPackUri(validHttpUri)).toEqual({
+ name: 'haileyok.com',
+ rkey: 'rkey',
+ })
+
+ const validHttpUri2 = 'https://bsky.app/starter-pack/haileyok.com/rkey'
+ expect(parseStarterPackUri(validHttpUri2)).toEqual({
+ name: 'haileyok.com',
+ rkey: 'rkey',
+ })
+
+ const invalidHttpUri = 'https://bsky.app/profile/haileyok.com/rkey'
+ expect(parseStarterPackUri(invalidHttpUri)).toEqual(null)
+ })
+
+ it('returns the at uri when the input is a valid starterpack at uri', () => {
+ const validAtUri = 'at://did:123/app.bsky.graph.starterpack/rkey'
+ expect(parseStarterPackUri(validAtUri)).toEqual({
+ name: 'did:123',
+ rkey: 'rkey',
+ })
+ })
+
+ it('returns null when the at uri has no rkey', () => {
+ const validAtUri = 'at://did:123/app.bsky.graph.starterpack'
+ expect(parseStarterPackUri(validAtUri)).toEqual(null)
+ })
+
+ it('returns null when the collection is not app.bsky.graph.starterpack', () => {
+ const validAtUri = 'at://did:123/app.bsky.graph.list/rkey'
+ expect(parseStarterPackUri(validAtUri)).toEqual(null)
+ })
+
+ it('returns null when the input is undefined', () => {
+ expect(parseStarterPackUri(undefined)).toEqual(null)
+ })
+})
+
+describe('createStarterPackGooglePlayUri', () => {
+ const base =
+ 'https://play.google.com/store/apps/details?id=xyz.blueskyweb.app&referrer=utm_source%3Dbluesky%26utm_medium%3Dstarterpack%26utm_content%3Dstarterpack_'
+
+ it('returns valid google play uri when input is valid', () => {
+ expect(createStarterPackGooglePlayUri('name', 'rkey')).toEqual(
+ `${base}name_rkey`,
+ )
+ })
+
+ it('returns null when no rkey is supplied', () => {
+ // @ts-expect-error test
+ expect(createStarterPackGooglePlayUri('name', undefined)).toEqual(null)
+ })
+
+ it('returns null when no name or rkey are supplied', () => {
+ // @ts-expect-error test
+ expect(createStarterPackGooglePlayUri(undefined, undefined)).toEqual(null)
+ })
+
+ it('returns null when rkey is supplied but no name', () => {
+ // @ts-expect-error test
+ expect(createStarterPackGooglePlayUri(undefined, 'rkey')).toEqual(null)
+ })
+})
diff --git a/app.config.js b/app.config.js
index eafacc6cc..57d430586 100644
--- a/app.config.js
+++ b/app.config.js
@@ -39,6 +39,17 @@ module.exports = function (config) {
const IS_TESTFLIGHT = process.env.EXPO_PUBLIC_ENV === 'testflight'
const IS_PRODUCTION = process.env.EXPO_PUBLIC_ENV === 'production'
+ const ASSOCIATED_DOMAINS = [
+ 'applinks:bsky.app',
+ 'applinks:staging.bsky.app',
+ 'appclips:bsky.app',
+ 'appclips:go.bsky.app', // Allows App Clip to work when scanning QR codes
+ // When testing local services, enter an ngrok (et al) domain here. It must use a standard HTTP/HTTPS port.
+ ...(IS_DEV || IS_TESTFLIGHT
+ ? ['appclips:sptesting.haileyok.com', 'applinks:sptesting.haileyok.com']
+ : []),
+ ]
+
const UPDATES_CHANNEL = IS_TESTFLIGHT
? 'testflight'
: IS_PRODUCTION
@@ -83,7 +94,7 @@ module.exports = function (config) {
NSPhotoLibraryUsageDescription:
'Used for profile pictures, posts, and other kinds of content',
},
- associatedDomains: ['applinks:bsky.app', 'applinks:staging.bsky.app'],
+ associatedDomains: ASSOCIATED_DOMAINS,
splash: {
...SPLASH_CONFIG,
dark: DARK_SPLASH_CONFIG,
@@ -202,6 +213,7 @@ module.exports = function (config) {
sounds: PLATFORM === 'ios' ? ['assets/dm.aiff'] : ['assets/dm.mp3'],
},
],
+ './plugins/starterPackAppClipExtension/withStarterPackAppClip.js',
'./plugins/withAndroidManifestPlugin.js',
'./plugins/withAndroidManifestFCMIconPlugin.js',
'./plugins/withAndroidStylesWindowBackgroundPlugin.js',
@@ -234,6 +246,10 @@ module.exports = function (config) {
],
},
},
+ {
+ targetName: 'BlueskyClip',
+ bundleIdentifier: 'xyz.blueskyweb.app.AppClip',
+ },
],
},
},
diff --git a/assets/icons/qrCode_stroke2_corner0_rounded.svg b/assets/icons/qrCode_stroke2_corner0_rounded.svg
new file mode 100644
index 000000000..b17db3953
--- /dev/null
+++ b/assets/icons/qrCode_stroke2_corner0_rounded.svg
@@ -0,0 +1 @@
+
diff --git a/assets/icons/starterPack.svg b/assets/icons/starterPack.svg
new file mode 100644
index 000000000..7f0df5595
--- /dev/null
+++ b/assets/icons/starterPack.svg
@@ -0,0 +1 @@
+
diff --git a/assets/icons/starter_pack_icon.svg b/assets/icons/starter_pack_icon.svg
new file mode 100644
index 000000000..47a2f49b6
--- /dev/null
+++ b/assets/icons/starter_pack_icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/logo.png b/assets/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..cc2d660341e380d1150b1f86735ac78d8394e87f
GIT binary patch
literal 10126
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