Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/activitypub/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tryghost/activitypub",
"version": "1.1.0",
"version": "2.0.0",
"license": "MIT",
"repository": {
"type": "git",
Expand Down
87 changes: 58 additions & 29 deletions apps/activitypub/src/api/activitypub.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1607,10 +1607,11 @@ describe('ActivityPubAPI', function () {
}]
})
},
[`https://activitypub.api/.ghost/activitypub/v1/actions/bluesky/enable`]: {
response: JSONResponse({
handle: '@foo@bar.baz'
})
[`https://activitypub.api/.ghost/activitypub/v2/actions/bluesky/enable`]: {
async assert(_resource, init) {
expect(init?.method).toEqual('POST');
},
response: JSONResponse(null)
}
});

Expand All @@ -1621,12 +1622,12 @@ describe('ActivityPubAPI', function () {
fakeFetch
);

const result = await api.enableBluesky();

expect(result).toBe('@foo@bar.baz');
await api.enableBluesky();
});
});

test('It returns an empty string if the response is null', async function () {
describe('disableBluesky', function () {
test('It disables bluesky', async function () {
const fakeFetch = Fetch({
'https://auth.api/': {
response: JSONResponse({
Expand All @@ -1635,7 +1636,10 @@ describe('ActivityPubAPI', function () {
}]
})
},
[`https://activitypub.api/.ghost/activitypub/v1/actions/bluesky/enable`]: {
[`https://activitypub.api/.ghost/activitypub/v2/actions/bluesky/disable`]: {
async assert(_resource, init) {
expect(init?.method).toEqual('POST');
},
response: JSONResponse(null)
}
});
Expand All @@ -1647,12 +1651,12 @@ describe('ActivityPubAPI', function () {
fakeFetch
);

const result = await api.enableBluesky();

expect(result).toBe('');
await api.disableBluesky();
});
});

test('It returns an empty string if the response does not contain a handle property', async function () {
describe('confirmBlueskyHandle', function () {
test('It confirms the bluesky handle', async function () {
const fakeFetch = Fetch({
'https://auth.api/': {
response: JSONResponse({
Expand All @@ -1661,9 +1665,9 @@ describe('ActivityPubAPI', function () {
}]
})
},
[`https://activitypub.api/.ghost/activitypub/v1/actions/bluesky/enable`]: {
[`https://activitypub.api/.ghost/activitypub/v2/actions/bluesky/confirm-handle`]: {
response: JSONResponse({
foo: 'bar'
handle: 'foo@bar.baz'
})
}
});
Expand All @@ -1675,12 +1679,38 @@ describe('ActivityPubAPI', function () {
fakeFetch
);

const result = await api.enableBluesky();
const result = await api.confirmBlueskyHandle();

expect(result).toBe('foo@bar.baz');
});

test('It returns an empty string if the response is null', async function () {
const fakeFetch = Fetch({
'https://auth.api/': {
response: JSONResponse({
identities: [{
token: 'fake-token'
}]
})
},
[`https://activitypub.api/.ghost/activitypub/v2/actions/bluesky/confirm-handle`]: {
response: JSONResponse(null)
}
});

const api = new ActivityPubAPI(
new URL('https://activitypub.api'),
new URL('https://auth.api'),
'index',
fakeFetch
);

const result = await api.confirmBlueskyHandle();

expect(result).toBe('');
});

test('It returns an empty string if the response contains an invalid handle property', async function () {
test('It returns an empty string if the response does not contain a handle property', async function () {
const fakeFetch = Fetch({
'https://auth.api/': {
response: JSONResponse({
Expand All @@ -1689,9 +1719,9 @@ describe('ActivityPubAPI', function () {
}]
})
},
[`https://activitypub.api/.ghost/activitypub/v1/actions/bluesky/enable`]: {
[`https://activitypub.api/.ghost/activitypub/v2/actions/bluesky/confirm-handle`]: {
response: JSONResponse({
handle: ['@foo@bar.baz']
foo: 'bar'
})
}
});
Expand All @@ -1703,14 +1733,12 @@ describe('ActivityPubAPI', function () {
fakeFetch
);

const result = await api.enableBluesky();
const result = await api.confirmBlueskyHandle();

expect(result).toBe('');
});
});

describe('disableBluesky', function () {
test('It disables bluesky', async function () {
test('It returns an empty string if the response contains an invalid handle property', async function () {
const fakeFetch = Fetch({
'https://auth.api/': {
response: JSONResponse({
Expand All @@ -1719,11 +1747,10 @@ describe('ActivityPubAPI', function () {
}]
})
},
[`https://activitypub.api/.ghost/activitypub/v1/actions/bluesky/disable`]: {
async assert(_resource, init) {
expect(init?.method).toEqual('POST');
},
response: JSONResponse(null)
[`https://activitypub.api/.ghost/activitypub/v2/actions/bluesky/confirm-handle`]: {
response: JSONResponse({
handle: ['foo@bar.baz']
})
}
});

Expand All @@ -1734,7 +1761,9 @@ describe('ActivityPubAPI', function () {
fakeFetch
);

await api.disableBluesky();
const result = await api.confirmBlueskyHandle();

expect(result).toBe('');
});
});
});
25 changes: 16 additions & 9 deletions apps/activitypub/src/api/activitypub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ export interface Account {
domainBlockedByMe: boolean;
attachment: { name: string; value: string }[];
blueskyEnabled?: boolean;
blueskyHandle?: string;
blueskyHandleConfirmed?: boolean;
blueskyHandle?: string | null;
}

export type AccountSearchResult = Pick<
Expand Down Expand Up @@ -707,8 +708,20 @@ export class ActivityPubAPI {
return json.fileUrl;
}

async enableBluesky(): Promise<string> {
const url = new URL('.ghost/activitypub/v1/actions/bluesky/enable', this.apiUrl);
async enableBluesky() {
const url = new URL('.ghost/activitypub/v2/actions/bluesky/enable', this.apiUrl);

await this.fetchJSON(url, 'POST');
}

async disableBluesky() {
const url = new URL('.ghost/activitypub/v2/actions/bluesky/disable', this.apiUrl);

await this.fetchJSON(url, 'POST');
}

async confirmBlueskyHandle(): Promise<string> {
const url = new URL('.ghost/activitypub/v2/actions/bluesky/confirm-handle', this.apiUrl);

const json = await this.fetchJSON(url, 'POST');

Expand All @@ -718,10 +731,4 @@ export class ActivityPubAPI {

return String(json.handle);
}

async disableBluesky() {
const url = new URL('.ghost/activitypub/v1/actions/bluesky/disable', this.apiUrl);

await this.fetchJSON(url, 'POST');
}
}
58 changes: 51 additions & 7 deletions apps/activitypub/src/hooks/use-activity-pub-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2722,18 +2722,23 @@ export function useSuggestedProfilesForUser(handle: string, limit = 3) {
return {suggestedProfilesQuery, updateSuggestedProfile};
}

function updateAccountBlueskyCache(queryClient: QueryClient, blueskyHandle: string | null) {
type BlueskyDetails = {
blueskyEnabled: boolean;
blueskyHandleConfirmed: boolean;
blueskyHandle: string | null;
}

function updateAccountBlueskyCache(queryClient: QueryClient, blueskyDetails: BlueskyDetails) {
const profileQueryKey = QUERY_KEYS.account('index');

queryClient.setQueryData(profileQueryKey, (currentProfile?: {blueskyEnabled: boolean, blueskyHandle: string | null}) => {
queryClient.setQueryData(profileQueryKey, (currentProfile?: BlueskyDetails) => {
if (!currentProfile) {
return currentProfile;
}

return {
...currentProfile,
blueskyEnabled: blueskyHandle !== null,
blueskyHandle
...blueskyDetails
};
});
}
Expand All @@ -2748,8 +2753,12 @@ export function useEnableBlueskyMutationForUser(handle: string) {

return api.enableBluesky();
},
onSuccess(blueskyHandle: string) {
updateAccountBlueskyCache(queryClient, blueskyHandle);
onSuccess() {
updateAccountBlueskyCache(queryClient, {
blueskyEnabled: true,
blueskyHandleConfirmed: false,
blueskyHandle: null
});

// Invalidate the following query as enabling bluesky will cause
// the account to follow the brid.gy account (and we want this to
Expand Down Expand Up @@ -2777,7 +2786,11 @@ export function useDisableBlueskyMutationForUser(handle: string) {
return api.disableBluesky();
},
onSuccess() {
updateAccountBlueskyCache(queryClient, null);
updateAccountBlueskyCache(queryClient, {
blueskyEnabled: false,
blueskyHandleConfirmed: false,
blueskyHandle: null
});

// Invalidate the following query as disabling bluesky will cause
// the account to unfollow the brid.gy account (and we want this to
Expand All @@ -2793,3 +2806,34 @@ export function useDisableBlueskyMutationForUser(handle: string) {
}
});
}

export function useConfirmBlueskyHandleMutationForUser(handle: string) {
const queryClient = useQueryClient();

return useMutation({
async mutationFn() {
const siteUrl = await getSiteUrl();
const api = createActivityPubAPI(handle, siteUrl);

return api.confirmBlueskyHandle();
},
onSuccess(blueskyHandle: string) {
// If the bluesky handle is empty then the handle was not confirmed
// so we don't need to update the cache
if (blueskyHandle === '') {
return;
}

updateAccountBlueskyCache(queryClient, {
blueskyEnabled: true,
blueskyHandleConfirmed: true,
blueskyHandle: blueskyHandle
});
},
onError(error: {message: string, statusCode: number}) {
if (error.statusCode === 429) {
renderRateLimitError();
}
}
});
}
Loading
Loading