diff --git a/app.config.js b/app.config.js index 83fe9d7e86..c1ce5c7c76 100644 --- a/app.config.js +++ b/app.config.js @@ -119,6 +119,7 @@ module.exports = function (_config) { 'com.apple.developer.kernel.increased-memory-limit': true, 'com.apple.developer.kernel.extended-virtual-addressing': true, 'com.apple.security.application-groups': 'group.app.bsky', + 'com.apple.developer.device-information.user-assigned-device-name': true, }, privacyManifests: { NSPrivacyCollectedDataTypes: [ diff --git a/package.json b/package.json index e8baad5c50..6a1b9e89cf 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "icons:optimize": "svgo -f ./assets/icons" }, "dependencies": { - "@atproto/api": "^0.18.18", + "@atproto/api": "^0.18.20", "@bitdrift/react-native": "^0.6.8", "@braintree/sanitize-url": "^6.0.2", "@bsky.app/alf": "^0.1.6", diff --git a/src/lib/deviceName.ts b/src/lib/deviceName.ts new file mode 100644 index 0000000000..7ae4c845a5 --- /dev/null +++ b/src/lib/deviceName.ts @@ -0,0 +1,16 @@ +import * as Device from 'expo-device' + +import * as env from '#/env' + +export function getDeviceName(): string { + const deviceName = Device.deviceName + if (env.IS_ANDROID) { + return deviceName || 'Android' + } else if (env.IS_IOS) { + // we need an entitlement to get the real device name on iOS, so just + // return a generic name for now + return 'iOS' + } else { + return 'Web' // could append browser info here + } +} diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index 148811bc15..d73d9c7b27 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -138,7 +138,7 @@ import { type RestoredVideo, } from './drafts/state/api' import { - loadDraft, + loadDraftMedia, useCleanupPublishedDraftMutation, useSaveDraftMutation, } from './drafts/state/queries' @@ -482,7 +482,7 @@ export const ComposePost = ({ }) // Load local media files for the draft - const {loadedMedia} = await loadDraft(draftSummary.draft) + const {loadedMedia} = await loadDraftMedia(draftSummary.draft) // Extract original localRefs for orphan detection on save const originalLocalRefs = extractLocalRefs(draftSummary.draft) diff --git a/src/view/com/composer/drafts/state/api.ts b/src/view/com/composer/drafts/state/api.ts index 3cc52f6fd4..d45f573cea 100644 --- a/src/view/com/composer/drafts/state/api.ts +++ b/src/view/com/composer/drafts/state/api.ts @@ -1,11 +1,11 @@ /** * Type converters for Draft API - convert between ComposerState and server Draft types. */ -import {Platform} from 'react-native' import {type AppBskyDraftDefs, AtUri, RichText} from '@atproto/api' import {nanoid} from 'nanoid/non-secure' import {resolveLink} from '#/lib/api/resolve' +import {getDeviceName} from '#/lib/deviceName' import {getImageDim} from '#/lib/media/manip' import {mimeToExt} from '#/lib/media/video/util' import {type ComposerImage} from '#/state/gallery' @@ -69,7 +69,7 @@ export async function composerStateToDraft(state: ComposerState): Promise<{ const draft: AppBskyDraftDefs.Draft = { $type: 'app.bsky.draft.defs#draft', deviceId: getDeviceId(), - platform: Platform.OS, + deviceName: getDeviceName().slice(0, 100), // max length of 100 in lex posts, threadgateAllow: threadgateAllowUISettingToAllowRecordValue( state.thread.threadgate, diff --git a/src/view/com/composer/drafts/state/queries.ts b/src/view/com/composer/drafts/state/queries.ts index 2694c7ddab..106c12ab5e 100644 --- a/src/view/com/composer/drafts/state/queries.ts +++ b/src/view/com/composer/drafts/state/queries.ts @@ -8,6 +8,7 @@ import { import {isNetworkError} from '#/lib/strings/errors' import {useAgent} from '#/state/session' import {type ComposerState} from '#/view/com/composer/state/composer' +import {getDeviceId} from '#/analytics/identifiers' import {composerStateToDraft, draftViewToSummary} from './api' import {logger} from './logger' import * as storage from './storage' @@ -42,11 +43,17 @@ export function useDraftsQuery() { * Load a draft's local media for editing. * Takes the full Draft object (from DraftSummary) to avoid re-fetching. */ -export async function loadDraft(draft: AppBskyDraftDefs.Draft): Promise<{ +export async function loadDraftMedia(draft: AppBskyDraftDefs.Draft): Promise<{ loadedMedia: Map }> { // Load local media files const loadedMedia = new Map() + + // can't load media from another device + if (draft.deviceId && draft.deviceId !== getDeviceId()) { + return {loadedMedia} + } + for (const post of draft.posts) { // Load images if (post.embedImages) { @@ -54,10 +61,10 @@ export async function loadDraft(draft: AppBskyDraftDefs.Draft): Promise<{ try { const url = await storage.loadMediaFromLocal(img.localRef.path) loadedMedia.set(img.localRef.path, url) - } catch (e) { - logger.debug('Failed to load draft image', { + } catch (e: any) { + logger.error('Failed to load draft image', { path: img.localRef.path, - error: e, + safeMessage: e.message, }) } } @@ -68,10 +75,10 @@ export async function loadDraft(draft: AppBskyDraftDefs.Draft): Promise<{ try { const url = await storage.loadMediaFromLocal(vid.localRef.path) loadedMedia.set(vid.localRef.path, url) - } catch (e) { - logger.debug('Failed to load draft video', { + } catch (e: any) { + logger.error('Failed to load draft video', { path: vid.localRef.path, - error: e, + safeMessage: e.message, }) } } diff --git a/yarn.lock b/yarn.lock index 830107d7f7..f50ea1d5c0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -96,6 +96,20 @@ tlds "^1.234.0" zod "^3.23.8" +"@atproto/api@^0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.18.20.tgz#3fdbb7b7fae90bd59101970c2b56cc31e8cf417d" + integrity sha512-BZYZkh2VJIFCXEnc/vzKwAwWjAQQTgbNJ8FBxpBK+z+KYh99O0uPCsRYKoCQsRrnkgrhzdU9+g2G+7zanTIGbw== + dependencies: + "@atproto/common-web" "^0.4.15" + "@atproto/lexicon" "^0.6.1" + "@atproto/syntax" "^0.4.3" + "@atproto/xrpc" "^0.7.7" + await-lock "^2.2.2" + multiformats "^9.9.0" + tlds "^1.234.0" + zod "^3.23.8" + "@atproto/aws@^0.2.31": version "0.2.31" resolved "https://registry.yarnpkg.com/@atproto/aws/-/aws-0.2.31.tgz#e46d7db34ee57c4f9817269f1e73a7eddba2b9b8" @@ -190,6 +204,16 @@ "@atproto/syntax" "0.4.3" zod "^3.23.8" +"@atproto/common-web@^0.4.15": + version "0.4.15" + resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.4.15.tgz#1fffedf62d69b8c96f7b360e11c4180446a2cc82" + integrity sha512-A4l9gyqUNez8CjZp/Trypz/D3WIQsNj8dN05WR6+RoBbvwc9JhWjKPrm+WoVYc/F16RPdXHLkE3BEJlGIyYIiA== + dependencies: + "@atproto/lex-data" "^0.0.10" + "@atproto/lex-json" "^0.0.10" + "@atproto/syntax" "^0.4.3" + zod "^3.23.8" + "@atproto/common@0.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.1.0.tgz#4216a8fef5b985ab62ac21252a0f8ca0f4a0f210" @@ -327,6 +351,16 @@ uint8arrays "3.0.0" unicode-segmenter "^0.14.0" +"@atproto/lex-data@^0.0.10": + version "0.0.10" + resolved "https://registry.yarnpkg.com/@atproto/lex-data/-/lex-data-0.0.10.tgz#091c012af817a869951ce0e61eb757bd6c29797a" + integrity sha512-FDbcy8VIUVzS9Mi1F8SMxbkL/jOUmRRpqbeM1xB4A0fMxeZJTxf6naAbFt4gYF3quu/+TPJGmio6/7cav05FqQ== + dependencies: + multiformats "^9.9.0" + tslib "^2.8.1" + uint8arrays "3.0.0" + unicode-segmenter "^0.14.0" + "@atproto/lex-document@0.0.11": version "0.0.11" resolved "https://registry.yarnpkg.com/@atproto/lex-document/-/lex-document-0.0.11.tgz#8cfdd6ab5b5befac4d1409c76e2d5a310845c1dc" @@ -344,6 +378,14 @@ "@atproto/lex-data" "0.0.9" tslib "^2.8.1" +"@atproto/lex-json@^0.0.10": + version "0.0.10" + resolved "https://registry.yarnpkg.com/@atproto/lex-json/-/lex-json-0.0.10.tgz#18de1c8cc3ba564e5412bce1f8e22c198c956e64" + integrity sha512-L6MyXU17C5ODMeob8myQ2F3xvgCTvJUtM0ew8qSApnN//iDasB/FDGgd7ty4UVNmx4NQ/rtvz8xV94YpG6kneQ== + dependencies: + "@atproto/lex-data" "^0.0.10" + tslib "^2.8.1" + "@atproto/lex-resolver@0.0.12": version "0.0.12" resolved "https://registry.yarnpkg.com/@atproto/lex-resolver/-/lex-resolver-0.0.12.tgz#fb6cd78c78c0acfc9a92d9e42abe7ff18b4c3a41"