parent
98741fb49c
commit
f9af5e0216
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@status-im/js': patch
|
||||
---
|
||||
|
||||
validate encoded data
|
|
@ -32,7 +32,34 @@ describe('Encode URL data', () => {
|
|||
expect(decodedData).toEqual(data)
|
||||
})
|
||||
|
||||
test('should encode and decode channel', () => {
|
||||
test('should throw for invalid community data', () => {
|
||||
expect(() => {
|
||||
const encodedData = 'Ow=='
|
||||
decodeCommunityURLData(encodedData)
|
||||
}).toThrowError()
|
||||
})
|
||||
|
||||
test('should throw for unsupported data length', () => {
|
||||
expect(() => {
|
||||
const encodedData =
|
||||
'G2QBQJwFdqwxrBnNb57kP0irrJpuouIjS1WZqHS6A2txojsUHidyu3evaAO3GQQku5NCQXiwAYchBIMNyptts=MD9bZAwoTasraIMkjbS1uAD7oxsAQ53OAmQWCefyBuuXlAu6J7eKQRQhgg5tan75fFp9jwGIjBLbGhnyUht2qj5GWlSBp7_OXsHxgnr21xA2HgR9VGYYikQJA4tcQHDrQzg_ARC9KiOVDD6vgTCM9_CN0HJ1zxwP3w6nzgkDTNuvDCFD3Clqo6Cf_UNY2cNRlKTqj86G4gC2dUNSApwiq72BdGTtrleiRFPUhCbTRbmEG4YwFOs4EjBdJHHRiqjS5GYGc1dAdgcGr2BQ==============================================================================================================================================='
|
||||
decodeCommunityURLData(encodedData)
|
||||
}).toThrowErrorMatchingInlineSnapshot(`
|
||||
"[
|
||||
{
|
||||
\\"code\\": \\"too_big\\",
|
||||
\\"maximum\\": 500,
|
||||
\\"type\\": \\"string\\",
|
||||
\\"inclusive\\": true,
|
||||
\\"exact\\": false,
|
||||
\\"message\\": \\"String must contain at most 500 character(s)\\",
|
||||
\\"path\\": []
|
||||
}
|
||||
]"
|
||||
`)
|
||||
})
|
||||
|
||||
test('should encode and decode channel', () => {
|
||||
const data = {
|
||||
emoji: '🏴',
|
||||
displayName: 'lorem-ipsum-dolore-nulla',
|
||||
|
@ -56,6 +83,70 @@ describe('Encode URL data', () => {
|
|||
expect(decodedData).toEqual(data)
|
||||
})
|
||||
|
||||
test('should throw for invalid channel data', () => {
|
||||
expect(() => {
|
||||
const encodedData = 'Ow=='
|
||||
decodeChannelURLData(encodedData)
|
||||
}).toThrowErrorMatchingInlineSnapshot(`
|
||||
"[
|
||||
{
|
||||
\\"code\\": \\"invalid_type\\",
|
||||
\\"expected\\": \\"string\\",
|
||||
\\"received\\": \\"undefined\\",
|
||||
\\"path\\": [
|
||||
\\"displayName\\"
|
||||
],
|
||||
\\"message\\": \\"Required\\"
|
||||
},
|
||||
{
|
||||
\\"code\\": \\"invalid_type\\",
|
||||
\\"expected\\": \\"string\\",
|
||||
\\"received\\": \\"undefined\\",
|
||||
\\"path\\": [
|
||||
\\"description\\"
|
||||
],
|
||||
\\"message\\": \\"Required\\"
|
||||
},
|
||||
{
|
||||
\\"code\\": \\"invalid_type\\",
|
||||
\\"expected\\": \\"string\\",
|
||||
\\"received\\": \\"undefined\\",
|
||||
\\"path\\": [
|
||||
\\"emoji\\"
|
||||
],
|
||||
\\"message\\": \\"Required\\"
|
||||
},
|
||||
{
|
||||
\\"code\\": \\"invalid_type\\",
|
||||
\\"expected\\": \\"string\\",
|
||||
\\"received\\": \\"undefined\\",
|
||||
\\"path\\": [
|
||||
\\"color\\"
|
||||
],
|
||||
\\"message\\": \\"Required\\"
|
||||
},
|
||||
{
|
||||
\\"code\\": \\"invalid_type\\",
|
||||
\\"expected\\": \\"object\\",
|
||||
\\"received\\": \\"undefined\\",
|
||||
\\"path\\": [
|
||||
\\"community\\"
|
||||
],
|
||||
\\"message\\": \\"Required\\"
|
||||
},
|
||||
{
|
||||
\\"code\\": \\"invalid_type\\",
|
||||
\\"expected\\": \\"string\\",
|
||||
\\"received\\": \\"undefined\\",
|
||||
\\"path\\": [
|
||||
\\"uuid\\"
|
||||
],
|
||||
\\"message\\": \\"Required\\"
|
||||
}
|
||||
]"
|
||||
`)
|
||||
})
|
||||
|
||||
test('should encode and decode user', () => {
|
||||
const data = {
|
||||
displayName: 'Lorem ipsum dolore nulla',
|
||||
|
@ -72,4 +163,68 @@ describe('Encode URL data', () => {
|
|||
)
|
||||
expect(decodedData).toEqual(data)
|
||||
})
|
||||
|
||||
test('should throw for invalid user data', () => {
|
||||
expect(() => {
|
||||
const encodedData = 'Ow=='
|
||||
decodeChannelURLData(encodedData)
|
||||
}).toThrowErrorMatchingInlineSnapshot(`
|
||||
"[
|
||||
{
|
||||
\\"code\\": \\"invalid_type\\",
|
||||
\\"expected\\": \\"string\\",
|
||||
\\"received\\": \\"undefined\\",
|
||||
\\"path\\": [
|
||||
\\"displayName\\"
|
||||
],
|
||||
\\"message\\": \\"Required\\"
|
||||
},
|
||||
{
|
||||
\\"code\\": \\"invalid_type\\",
|
||||
\\"expected\\": \\"string\\",
|
||||
\\"received\\": \\"undefined\\",
|
||||
\\"path\\": [
|
||||
\\"description\\"
|
||||
],
|
||||
\\"message\\": \\"Required\\"
|
||||
},
|
||||
{
|
||||
\\"code\\": \\"invalid_type\\",
|
||||
\\"expected\\": \\"string\\",
|
||||
\\"received\\": \\"undefined\\",
|
||||
\\"path\\": [
|
||||
\\"emoji\\"
|
||||
],
|
||||
\\"message\\": \\"Required\\"
|
||||
},
|
||||
{
|
||||
\\"code\\": \\"invalid_type\\",
|
||||
\\"expected\\": \\"string\\",
|
||||
\\"received\\": \\"undefined\\",
|
||||
\\"path\\": [
|
||||
\\"color\\"
|
||||
],
|
||||
\\"message\\": \\"Required\\"
|
||||
},
|
||||
{
|
||||
\\"code\\": \\"invalid_type\\",
|
||||
\\"expected\\": \\"object\\",
|
||||
\\"received\\": \\"undefined\\",
|
||||
\\"path\\": [
|
||||
\\"community\\"
|
||||
],
|
||||
\\"message\\": \\"Required\\"
|
||||
},
|
||||
{
|
||||
\\"code\\": \\"invalid_type\\",
|
||||
\\"expected\\": \\"string\\",
|
||||
\\"received\\": \\"undefined\\",
|
||||
\\"path\\": [
|
||||
\\"uuid\\"
|
||||
],
|
||||
\\"message\\": \\"Required\\"
|
||||
}
|
||||
]"
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { base64url } from '@scure/base'
|
||||
import { brotliCompressSync, brotliDecompressSync } from 'zlib'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { Channel, Community, URLData, User } from '../protos/url_pb'
|
||||
|
||||
|
@ -7,6 +8,17 @@ import type { PlainMessage } from '@bufbuild/protobuf'
|
|||
|
||||
export type EncodedURLData = string & { _: 'EncodedURLData' }
|
||||
|
||||
const colorSchema = z.string().regex(/^#[0-9A-Fa-f]{6}$/)
|
||||
const communityDisplayName = z.string().max(30).nonempty()
|
||||
|
||||
const communitySchema = z.object({
|
||||
displayName: communityDisplayName,
|
||||
description: z.string().max(140).nonempty(),
|
||||
membersCount: z.number().nonnegative(),
|
||||
color: colorSchema,
|
||||
tagIndices: z.number().nonnegative().array(),
|
||||
})
|
||||
|
||||
export function encodeCommunityURLData(
|
||||
data: PlainMessage<Community>
|
||||
): EncodedURLData {
|
||||
|
@ -17,25 +29,48 @@ export function encodeCommunityURLData(
|
|||
export function decodeCommunityURLData(data: string): PlainMessage<Community> {
|
||||
const deserialized = decodeURLData(data)
|
||||
|
||||
return Community.fromBinary(
|
||||
deserialized.content
|
||||
).toJson() as PlainMessage<Community>
|
||||
const community = Community.fromBinary(deserialized.content).toJson()
|
||||
|
||||
return communitySchema.parse(community)
|
||||
}
|
||||
|
||||
const channelSchema = z.object({
|
||||
displayName: z.string().max(24).nonempty(),
|
||||
description: z.string().max(140).nonempty(),
|
||||
emoji: z.string().emoji(),
|
||||
color: colorSchema,
|
||||
community: z.object({
|
||||
displayName: communityDisplayName,
|
||||
}),
|
||||
uuid: z.string().uuid(),
|
||||
})
|
||||
|
||||
export function encodeChannelURLData(
|
||||
data: PlainMessage<Channel>
|
||||
): EncodedURLData {
|
||||
return encodeURLData(new Channel(data).toBinary()) as EncodedURLData
|
||||
}
|
||||
|
||||
export function decodeChannelURLData(data: string): PlainMessage<Channel> {
|
||||
export function decodeChannelURLData(data: string): Omit<
|
||||
PlainMessage<Channel>,
|
||||
'community'
|
||||
> & {
|
||||
community: Pick<PlainMessage<Community>, 'displayName'>
|
||||
} {
|
||||
const deserialized = decodeURLData(data)
|
||||
|
||||
return Channel.fromBinary(
|
||||
deserialized.content
|
||||
).toJson() as PlainMessage<Channel>
|
||||
const channel = Channel.fromBinary(deserialized.content).toJson()
|
||||
|
||||
return channelSchema.parse(channel)
|
||||
}
|
||||
|
||||
const userSchema = z.object({
|
||||
displayName: z.string().max(24).nonempty(),
|
||||
description: z.string().max(240).nonempty(),
|
||||
// fixme: await integration in native platforms
|
||||
color: colorSchema.optional().default('#ffffff'),
|
||||
})
|
||||
|
||||
export function encodeUserURLData(data: PlainMessage<User>): EncodedURLData {
|
||||
return encodeURLData(new User(data).toBinary()) as EncodedURLData
|
||||
}
|
||||
|
@ -43,7 +78,9 @@ export function encodeUserURLData(data: PlainMessage<User>): EncodedURLData {
|
|||
export function decodeUserURLData(data: string): PlainMessage<User> {
|
||||
const deserialized = decodeURLData(data)
|
||||
|
||||
return User.fromBinary(deserialized.content).toJson() as PlainMessage<User>
|
||||
const user = User.fromBinary(deserialized.content).toJson()
|
||||
|
||||
return userSchema.parse(user)
|
||||
}
|
||||
|
||||
function encodeURLData(data: Uint8Array): string {
|
||||
|
@ -57,6 +94,11 @@ function encodeURLData(data: Uint8Array): string {
|
|||
}
|
||||
|
||||
function decodeURLData(data: string): URLData {
|
||||
// note: https://github.com/status-im/status-web/pull/345#discussion_r1113129396 observed lengths
|
||||
// note?: https://docs.google.com/spreadsheets/d/1JD4kp0aUm90piUZ7FgM_c2NGe2PdN8BFB11wmt5UZIY/view#gid=1260088614 limit for url path segmets not split by ";" or "_"
|
||||
// fixme: set to 301 per url path segment when the above mentioned splitting is implemented
|
||||
z.string().max(500).parse(data) // default max in order not to compute arbitrary values
|
||||
|
||||
const decoded = base64url.decode(data)
|
||||
const decompressed = brotliDecompressSync(decoded)
|
||||
const deserialized = URLData.fromBinary(decompressed)
|
||||
|
|
Loading…
Reference in New Issue