feat: bluesky
This commit is contained in:
parent
934296955a
commit
3bfac7a38d
|
|
@ -288,15 +288,19 @@ export class IntegrationsController {
|
|||
throw new Error('Integration not allowed');
|
||||
}
|
||||
|
||||
const getCodeVerifier = await ioRedis.get(`login:${body.state}`);
|
||||
const integrationProvider =
|
||||
this._integrationManager.getSocialIntegration(integration);
|
||||
|
||||
const getCodeVerifier = integrationProvider.customFields
|
||||
? 'none'
|
||||
: await ioRedis.get(`login:${body.state}`);
|
||||
if (!getCodeVerifier) {
|
||||
throw new Error('Invalid state');
|
||||
}
|
||||
|
||||
await ioRedis.del(`login:${body.state}`);
|
||||
|
||||
const integrationProvider =
|
||||
this._integrationManager.getSocialIntegration(integration);
|
||||
if (!integrationProvider.customFields) {
|
||||
await ioRedis.del(`login:${body.state}`);
|
||||
}
|
||||
|
||||
const details = integrationProvider.externalUrl
|
||||
? await ioRedis.get(`external:${body.state}`)
|
||||
|
|
@ -341,7 +345,13 @@ export class IntegrationsController {
|
|||
integrationProvider.isBetweenSteps,
|
||||
body.refresh,
|
||||
+body.timezone,
|
||||
details ? AuthService.fixedEncryption(details) : undefined
|
||||
details
|
||||
? AuthService.fixedEncryption(details)
|
||||
: integrationProvider.customFields
|
||||
? AuthService.fixedEncryption(
|
||||
Buffer.from(body.code, 'base64').toString()
|
||||
)
|
||||
: undefined
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
|
|
@ -4,6 +4,7 @@ export const dynamic = 'force-dynamic';
|
|||
|
||||
import { internalFetch } from '@gitroom/helpers/utils/internal.fetch';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { Redirect } from '@gitroom/frontend/components/layout/redirect';
|
||||
|
||||
export default async function Page({
|
||||
params: { provider },
|
||||
|
|
@ -30,6 +31,22 @@ export default async function Page({
|
|||
return redirect(`/launches?scope=missing`);
|
||||
}
|
||||
|
||||
if (
|
||||
data.status !== HttpStatusCode.Ok &&
|
||||
data.status !== HttpStatusCode.Created
|
||||
) {
|
||||
return (
|
||||
<>
|
||||
<div className="mt-[50px] text-[50px]">
|
||||
Could not add provider.
|
||||
<br />
|
||||
You are being redirected back
|
||||
</div>
|
||||
<Redirect url="/launches" delay={3000} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const { inBetweenSteps, id } = await data.json();
|
||||
|
||||
if (inBetweenSteps && !searchParams.refresh) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import { useModals } from '@mantine/modals';
|
||||
import React, { FC, useCallback } from 'react';
|
||||
import React, { FC, useCallback, useMemo } from 'react';
|
||||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
import { Input } from '@gitroom/react/form/input';
|
||||
import { FieldValues, FormProvider, useForm } from 'react-hook-form';
|
||||
|
|
@ -12,6 +12,8 @@ import { useRouter } from 'next/navigation';
|
|||
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
|
||||
import { useVariables } from '@gitroom/react/helpers/variable.context';
|
||||
import { useToaster } from '@gitroom/react/toaster/toaster';
|
||||
import { object, string } from 'yup';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
|
||||
const resolver = classValidatorResolver(ApiKeyDto);
|
||||
|
||||
|
|
@ -181,55 +183,193 @@ export const UrlModal: FC<{
|
|||
);
|
||||
};
|
||||
|
||||
export const CustomVariables: FC<{
|
||||
variables: Array<{
|
||||
key: string;
|
||||
label: string;
|
||||
defaultValue?: string;
|
||||
validation: string;
|
||||
type: 'text' | 'password';
|
||||
}>;
|
||||
identifier: string;
|
||||
gotoUrl(url: string): void;
|
||||
}> = (props) => {
|
||||
const { gotoUrl, identifier, variables } = props;
|
||||
const modals = useModals();
|
||||
const schema = useMemo(() => {
|
||||
return object({
|
||||
...variables.reduce((aIcc, item) => {
|
||||
const splitter = item.validation.split('/');
|
||||
const regex = new RegExp(
|
||||
splitter.slice(1, -1).join('/'),
|
||||
splitter.pop()
|
||||
);
|
||||
return {
|
||||
...aIcc,
|
||||
[item.key]: string()
|
||||
.matches(regex, `${item.label} is invalid`)
|
||||
.required(),
|
||||
};
|
||||
}, {}),
|
||||
});
|
||||
}, [variables]);
|
||||
|
||||
const methods = useForm({
|
||||
mode: 'onChange',
|
||||
resolver: yupResolver(schema),
|
||||
values: variables.reduce(
|
||||
(acc, item) => ({
|
||||
...acc,
|
||||
...(item.defaultValue ? { [item.key]: item.defaultValue } : {}),
|
||||
}),
|
||||
{}
|
||||
),
|
||||
});
|
||||
|
||||
const submit = useCallback(
|
||||
async (data: FieldValues) => {
|
||||
gotoUrl(
|
||||
`/integrations/social/${identifier}?state=nostate&code=${Buffer.from(
|
||||
JSON.stringify(data)
|
||||
).toString('base64')}`
|
||||
);
|
||||
},
|
||||
[variables]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="rounded-[4px] border border-customColor6 bg-sixth px-[16px] pb-[16px] relative">
|
||||
<TopTitle title={`Custom URL`} />
|
||||
<button
|
||||
onClick={modals.closeAll}
|
||||
className="outline-none absolute right-[20px] top-[20px] mantine-UnstyledButton-root mantine-ActionIcon-root hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 15 15"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
>
|
||||
<path
|
||||
d="M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z"
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
<FormProvider {...methods}>
|
||||
<form
|
||||
className="gap-[8px] flex flex-col pt-[10px]"
|
||||
onSubmit={methods.handleSubmit(submit)}
|
||||
>
|
||||
{variables.map((variable) => (
|
||||
<div key={variable.key}>
|
||||
<Input
|
||||
label={variable.label}
|
||||
name={variable.key}
|
||||
type={variable.type == 'text' ? 'text' : 'password'}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<div>
|
||||
<Button type="submit">Connect</Button>
|
||||
</div>
|
||||
</form>
|
||||
</FormProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const AddProviderComponent: FC<{
|
||||
social: Array<{ identifier: string; name: string; isExternal: boolean }>;
|
||||
social: Array<{
|
||||
identifier: string;
|
||||
name: string;
|
||||
isExternal: boolean;
|
||||
customFields?: Array<{
|
||||
key: string;
|
||||
label: string;
|
||||
validation: string;
|
||||
type: 'text' | 'password';
|
||||
}>;
|
||||
}>;
|
||||
article: Array<{ identifier: string; name: string }>;
|
||||
update?: () => void;
|
||||
}> = (props) => {
|
||||
const { update } = props;
|
||||
const { update, social, article } = props;
|
||||
const { isGeneral } = useVariables();
|
||||
const toaster = useToaster();
|
||||
|
||||
const router = useRouter();
|
||||
const fetch = useFetch();
|
||||
const modal = useModals();
|
||||
const { social, article } = props;
|
||||
const getSocialLink = useCallback(
|
||||
(identifier: string, isExternal: boolean) => async () => {
|
||||
const gotoIntegration = async (externalUrl?: string) => {
|
||||
const { url, err } = await (
|
||||
await fetch(
|
||||
`/integrations/social/${identifier}${
|
||||
externalUrl ? `?externalUrl=${externalUrl}` : ``
|
||||
}`
|
||||
)
|
||||
).json();
|
||||
(
|
||||
identifier: string,
|
||||
isExternal: boolean,
|
||||
customFields?: Array<{
|
||||
key: string;
|
||||
label: string;
|
||||
validation: string;
|
||||
defaultValue?: string;
|
||||
type: 'text' | 'password';
|
||||
}>
|
||||
) =>
|
||||
async () => {
|
||||
const gotoIntegration = async (externalUrl?: string) => {
|
||||
const { url, err } = await (
|
||||
await fetch(
|
||||
`/integrations/social/${identifier}${
|
||||
externalUrl ? `?externalUrl=${externalUrl}` : ``
|
||||
}`
|
||||
)
|
||||
).json();
|
||||
|
||||
if (err) {
|
||||
toaster.show('Could not connect to the platform', 'warning');
|
||||
return ;
|
||||
if (err) {
|
||||
toaster.show('Could not connect to the platform', 'warning');
|
||||
return;
|
||||
}
|
||||
window.location.href = url;
|
||||
};
|
||||
|
||||
if (isExternal) {
|
||||
modal.closeAll();
|
||||
|
||||
modal.openModal({
|
||||
title: '',
|
||||
withCloseButton: false,
|
||||
classNames: {
|
||||
modal: 'bg-transparent text-textColor',
|
||||
},
|
||||
children: <UrlModal gotoUrl={gotoIntegration} />,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
window.location.href = url;
|
||||
};
|
||||
|
||||
if (isExternal) {
|
||||
modal.closeAll();
|
||||
if (customFields) {
|
||||
modal.closeAll();
|
||||
|
||||
modal.openModal({
|
||||
title: '',
|
||||
withCloseButton: false,
|
||||
classNames: {
|
||||
modal: 'bg-transparent text-textColor',
|
||||
},
|
||||
children: (
|
||||
<UrlModal gotoUrl={gotoIntegration} />
|
||||
),
|
||||
});
|
||||
modal.openModal({
|
||||
title: '',
|
||||
withCloseButton: false,
|
||||
classNames: {
|
||||
modal: 'bg-transparent text-textColor',
|
||||
},
|
||||
children: (
|
||||
<CustomVariables
|
||||
identifier={identifier}
|
||||
gotoUrl={(url: string) => router.push(url)}
|
||||
variables={customFields}
|
||||
/>
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await gotoIntegration();
|
||||
},
|
||||
await gotoIntegration();
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
|
|
@ -281,7 +421,11 @@ export const AddProviderComponent: FC<{
|
|||
{social.map((item) => (
|
||||
<div
|
||||
key={item.identifier}
|
||||
onClick={getSocialLink(item.identifier, item.isExternal)}
|
||||
onClick={getSocialLink(
|
||||
item.identifier,
|
||||
item.isExternal,
|
||||
item.customFields
|
||||
)}
|
||||
className={
|
||||
'w-[120px] h-[100px] bg-input text-textColor justify-center items-center flex flex-col gap-[10px] cursor-pointer'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
import { FC } from 'react';
|
||||
import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider';
|
||||
|
||||
const Empty: FC = (props) => {
|
||||
return null;
|
||||
};
|
||||
|
||||
export default withProvider(null, Empty, undefined, async (posts) => {
|
||||
if (posts.some((p) => p.length > 4)) {
|
||||
return 'There can be maximum 4 pictures in a post.';
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
|
@ -16,6 +16,7 @@ import ThreadsProvider from '@gitroom/frontend/components/launches/providers/thr
|
|||
import DiscordProvider from '@gitroom/frontend/components/launches/providers/discord/discord.provider';
|
||||
import SlackProvider from '@gitroom/frontend/components/launches/providers/slack/slack.provider';
|
||||
import MastodonProvider from '@gitroom/frontend/components/launches/providers/mastodon/mastodon.provider';
|
||||
import BlueskyProvider from '@gitroom/frontend/components/launches/providers/bluesky/bluesky.provider';
|
||||
|
||||
export const Providers = [
|
||||
{identifier: 'devto', component: DevtoProvider},
|
||||
|
|
@ -35,6 +36,7 @@ export const Providers = [
|
|||
{identifier: 'discord', component: DiscordProvider},
|
||||
{identifier: 'slack', component: SlackProvider},
|
||||
{identifier: 'mastodon', component: MastodonProvider},
|
||||
{identifier: 'bluesky', component: BlueskyProvider},
|
||||
];
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
'use client';
|
||||
|
||||
import { FC, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
export const Redirect: FC<{url: string, delay: number}> = (props) => {
|
||||
const { url, delay } = props;
|
||||
const router = useRouter();
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
router.push(url);
|
||||
}, delay);
|
||||
}, []);
|
||||
return null;
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ import { ThreadsProvider } from '@gitroom/nestjs-libraries/integrations/social/t
|
|||
import { DiscordProvider } from '@gitroom/nestjs-libraries/integrations/social/discord.provider';
|
||||
import { SlackProvider } from '@gitroom/nestjs-libraries/integrations/social/slack.provider';
|
||||
import { MastodonProvider } from '@gitroom/nestjs-libraries/integrations/social/mastodon.provider';
|
||||
import { BlueskyProvider } from '@gitroom/nestjs-libraries/integrations/social/bluesky.provider';
|
||||
// import { MastodonCustomProvider } from '@gitroom/nestjs-libraries/integrations/social/mastodon.custom.provider';
|
||||
|
||||
const socialIntegrationList: SocialProvider[] = [
|
||||
|
|
@ -35,6 +36,7 @@ const socialIntegrationList: SocialProvider[] = [
|
|||
new DiscordProvider(),
|
||||
new SlackProvider(),
|
||||
new MastodonProvider(),
|
||||
new BlueskyProvider(),
|
||||
// new MastodonCustomProvider(),
|
||||
];
|
||||
|
||||
|
|
@ -46,13 +48,16 @@ const articleIntegrationList = [
|
|||
|
||||
@Injectable()
|
||||
export class IntegrationManager {
|
||||
getAllIntegrations() {
|
||||
async getAllIntegrations() {
|
||||
return {
|
||||
social: socialIntegrationList.map((p) => ({
|
||||
name: p.name,
|
||||
identifier: p.identifier,
|
||||
isExternal: !!p.externalUrl
|
||||
})),
|
||||
social: await Promise.all(
|
||||
socialIntegrationList.map(async (p) => ({
|
||||
name: p.name,
|
||||
identifier: p.identifier,
|
||||
isExternal: !!p.externalUrl,
|
||||
...(p.customFields ? { customFields: await p.customFields() } : {}),
|
||||
}))
|
||||
),
|
||||
article: articleIntegrationList.map((p) => ({
|
||||
name: p.name,
|
||||
identifier: p.identifier,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,169 @@
|
|||
import {
|
||||
AuthTokenDetails,
|
||||
PostDetails,
|
||||
PostResponse,
|
||||
SocialProvider,
|
||||
} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';
|
||||
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
||||
import { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';
|
||||
import { BskyAgent } from '@atproto/api';
|
||||
import dayjs from 'dayjs';
|
||||
import { Integration } from '@prisma/client';
|
||||
import { AuthService } from '@gitroom/helpers/auth/auth.service';
|
||||
import sharp from 'sharp';
|
||||
|
||||
export class BlueskyProvider extends SocialAbstract implements SocialProvider {
|
||||
identifier = 'bluesky';
|
||||
name = 'Bluesky';
|
||||
isBetweenSteps = false;
|
||||
scopes = ['write:statuses', 'profile', 'write:media'];
|
||||
|
||||
async customFields() {
|
||||
return [
|
||||
{
|
||||
key: 'service',
|
||||
label: 'Service',
|
||||
defaultValue: 'https://bsky.social',
|
||||
validation: `/^(https?:\\/\\/)?((([a-zA-Z0-9\\-_]{1,256}\\.[a-zA-Z]{2,6})|(([0-9]{1,3}\\.){3}[0-9]{1,3}))(:[0-9]{1,5})?)(\\/[^\\s]*)?$/`,
|
||||
type: 'text' as const,
|
||||
},
|
||||
{
|
||||
key: 'identifier',
|
||||
label: 'Identifier',
|
||||
validation: `/^.{3,}$/`,
|
||||
type: 'text' as const,
|
||||
},
|
||||
{
|
||||
key: 'password',
|
||||
label: 'Password',
|
||||
validation: `/^.{3,}$/`,
|
||||
type: 'password' as const,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {
|
||||
return {
|
||||
refreshToken: '',
|
||||
expiresIn: 0,
|
||||
accessToken: '',
|
||||
id: '',
|
||||
name: '',
|
||||
picture: '',
|
||||
username: '',
|
||||
};
|
||||
}
|
||||
|
||||
async generateAuthUrl(refresh?: string) {
|
||||
const state = makeId(6);
|
||||
return {
|
||||
url: '',
|
||||
codeVerifier: makeId(10),
|
||||
state,
|
||||
};
|
||||
}
|
||||
|
||||
async authenticate(params: {
|
||||
code: string;
|
||||
codeVerifier: string;
|
||||
refresh?: string;
|
||||
}) {
|
||||
const body = JSON.parse(Buffer.from(params.code, 'base64').toString());
|
||||
|
||||
const agent = new BskyAgent({
|
||||
service: body.service,
|
||||
});
|
||||
|
||||
const {
|
||||
data: { accessJwt, refreshJwt, handle, did },
|
||||
} = await agent.login({
|
||||
identifier: body.identifier,
|
||||
password: body.password,
|
||||
});
|
||||
|
||||
const profile = await agent.getProfile({
|
||||
actor: did,
|
||||
});
|
||||
|
||||
return {
|
||||
refreshToken: refreshJwt,
|
||||
expiresIn: dayjs().add(100, 'years').unix() - dayjs().unix(),
|
||||
accessToken: accessJwt,
|
||||
id: did,
|
||||
name: profile.data.displayName!,
|
||||
picture: profile.data.avatar!,
|
||||
username: profile.data.handle!,
|
||||
};
|
||||
}
|
||||
|
||||
async post(
|
||||
id: string,
|
||||
accessToken: string,
|
||||
postDetails: PostDetails[],
|
||||
integration: Integration
|
||||
): Promise<PostResponse[]> {
|
||||
const body = JSON.parse(
|
||||
AuthService.fixedDecryption(integration.customInstanceDetails!)
|
||||
);
|
||||
const agent = new BskyAgent({
|
||||
service: body.service,
|
||||
});
|
||||
|
||||
await agent.login({
|
||||
identifier: body.identifier,
|
||||
password: body.password,
|
||||
});
|
||||
|
||||
let loadCid = '';
|
||||
let loadUri = '';
|
||||
for (const post of postDetails) {
|
||||
const images = await Promise.all(
|
||||
post.media?.map(async (p) => {
|
||||
return await agent.uploadBlob(
|
||||
new Blob([
|
||||
await sharp(await (await fetch(p.url)).arrayBuffer())
|
||||
.resize({ width: 400 })
|
||||
.toBuffer(),
|
||||
])
|
||||
);
|
||||
}) || []
|
||||
);
|
||||
|
||||
const { cid, uri } = await agent.post({
|
||||
text: post.message,
|
||||
createdAt: new Date().toISOString(),
|
||||
...(images.length
|
||||
? {
|
||||
embed: {
|
||||
$type: 'app.bsky.embed.images',
|
||||
images: images.map((p) => ({
|
||||
// can be an array up to 4 values
|
||||
alt: 'image', // the alt text
|
||||
image: p.data.blob,
|
||||
})),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(loadCid
|
||||
? {
|
||||
reply: {
|
||||
root: {
|
||||
uri: loadUri,
|
||||
cid: loadCid,
|
||||
},
|
||||
parent: {
|
||||
uri: loadUri,
|
||||
cid: loadCid,
|
||||
},
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
|
||||
loadCid = loadCid || cid;
|
||||
loadUri = loadUri || uri;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import {
|
||||
AuthTokenDetails,
|
||||
ClientInformation,
|
||||
PostDetails,
|
||||
PostResponse,
|
||||
SocialProvider,
|
||||
|
|
|
|||
|
|
@ -98,6 +98,15 @@ export interface SocialProvider
|
|||
ISocialMediaIntegration {
|
||||
identifier: string;
|
||||
refreshWait?: boolean;
|
||||
customFields?: () => Promise<
|
||||
{
|
||||
key: string;
|
||||
label: string;
|
||||
defaultValue?: string;
|
||||
validation: string;
|
||||
type: 'text' | 'password';
|
||||
}[]
|
||||
>;
|
||||
name: string;
|
||||
isBetweenSteps: boolean;
|
||||
scopes: string[];
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@atproto/api": "^0.13.11",
|
||||
"@aws-sdk/client-s3": "^3.410.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.410.0",
|
||||
"@casl/ability": "^6.5.0",
|
||||
|
|
@ -463,6 +464,58 @@
|
|||
"web-streams-polyfill": "^3.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@atproto/api": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/@atproto/api/-/api-0.13.11.tgz",
|
||||
"integrity": "sha512-YW+4WzZEGGj/SDYo9w+S2PkSaeSS+8Dosk21GFm4EFYq1eq7G0cxuMgvdcq6fov7f9zqsaTFQL2fA6cAgMA0ow==",
|
||||
"dependencies": {
|
||||
"@atproto/common-web": "^0.3.1",
|
||||
"@atproto/lexicon": "^0.4.2",
|
||||
"@atproto/syntax": "^0.3.0",
|
||||
"@atproto/xrpc": "^0.6.3",
|
||||
"await-lock": "^2.2.2",
|
||||
"multiformats": "^9.9.0",
|
||||
"tlds": "^1.234.0",
|
||||
"zod": "^3.23.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@atproto/common-web": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.3.1.tgz",
|
||||
"integrity": "sha512-N7wiTnus5vAr+lT//0y8m/FaHHLJ9LpGuEwkwDAeV3LCiPif4m/FS8x/QOYrx1PdZQwKso95RAPzCGWQBH5j6Q==",
|
||||
"dependencies": {
|
||||
"graphemer": "^1.4.0",
|
||||
"multiformats": "^9.9.0",
|
||||
"uint8arrays": "3.0.0",
|
||||
"zod": "^3.23.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@atproto/lexicon": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.4.2.tgz",
|
||||
"integrity": "sha512-CXoOkhcdF3XVUnR2oNgCs2ljWfo/8zUjxL5RIhJW/UNLp/FSl+KpF8Jm5fbk8Y/XXVPGRAsv9OYfxyU/14N/pw==",
|
||||
"dependencies": {
|
||||
"@atproto/common-web": "^0.3.1",
|
||||
"@atproto/syntax": "^0.3.0",
|
||||
"iso-datestring-validator": "^2.2.2",
|
||||
"multiformats": "^9.9.0",
|
||||
"zod": "^3.23.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@atproto/syntax": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.3.0.tgz",
|
||||
"integrity": "sha512-Weq0ZBxffGHDXHl9U7BQc2BFJi/e23AL+k+i5+D9hUq/bzT4yjGsrCejkjq0xt82xXDjmhhvQSZ0LqxyZ5woxA=="
|
||||
},
|
||||
"node_modules/@atproto/xrpc": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.6.3.tgz",
|
||||
"integrity": "sha512-S3tRvOdA9amPkKLll3rc4vphlDitLrkN5TwWh5Tu/jzk7mnobVVE3akYgICV9XCNHKjWM+IAPxFFI2qi+VW6nQ==",
|
||||
"dependencies": {
|
||||
"@atproto/lexicon": "^0.4.2",
|
||||
"zod": "^3.23.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-crypto/crc32": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz",
|
||||
|
|
@ -15780,6 +15833,11 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/await-lock": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/await-lock/-/await-lock-2.2.2.tgz",
|
||||
"integrity": "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw=="
|
||||
},
|
||||
"node_modules/axe-core": {
|
||||
"version": "4.10.0",
|
||||
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.0.tgz",
|
||||
|
|
@ -24646,6 +24704,11 @@
|
|||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
|
||||
},
|
||||
"node_modules/iso-datestring-validator": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz",
|
||||
"integrity": "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA=="
|
||||
},
|
||||
"node_modules/isobject": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
||||
|
|
@ -28889,6 +28952,11 @@
|
|||
"multicast-dns": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/multiformats": {
|
||||
"version": "9.9.0",
|
||||
"resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz",
|
||||
"integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="
|
||||
},
|
||||
"node_modules/mustache": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
|
||||
|
|
@ -36374,6 +36442,14 @@
|
|||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tlds": {
|
||||
"version": "1.255.0",
|
||||
"resolved": "https://registry.npmjs.org/tlds/-/tlds-1.255.0.tgz",
|
||||
"integrity": "sha512-tcwMRIioTcF/FcxLev8MJWxCp+GUALRhFEqbDoZrnowmKSGqPrl5pqS+Sut2m8BgJ6S4FExCSSpGffZ0Tks6Aw==",
|
||||
"bin": {
|
||||
"tlds": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/tldts": {
|
||||
"version": "6.1.47",
|
||||
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.47.tgz",
|
||||
|
|
@ -37068,6 +37144,14 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/uint8arrays": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.0.0.tgz",
|
||||
"integrity": "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==",
|
||||
"dependencies": {
|
||||
"multiformats": "^9.4.2"
|
||||
}
|
||||
},
|
||||
"node_modules/unbox-primitive": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@atproto/api": "^0.13.11",
|
||||
"@aws-sdk/client-s3": "^3.410.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.410.0",
|
||||
"@casl/ability": "^6.5.0",
|
||||
|
|
|
|||
Loading…
Reference in New Issue