diff --git a/bun.lock b/bun.lock index 60e4dd794..b698864ca 100644 --- a/bun.lock +++ b/bun.lock @@ -7,6 +7,7 @@ "zustand": "^5.0.9", }, "devDependencies": { + "@types/bun": "^1.3.5", "oxlint": "^0.16.0", }, }, @@ -66,6 +67,7 @@ "web": { "name": "@inline-chat/web", "dependencies": { + "@inline/client": "workspace:*", "@stylexjs/babel-plugin": "^0.16.0", "@stylexjs/postcss-plugin": "^0.16.0", "@stylexjs/stylex": "^0.16.0", @@ -77,7 +79,7 @@ "framer-motion": "^11.18.1", "isbot": "^5.1.17", "motion": "^12.23.22", - "nitro": "^3.0.1-alpha.1", + "nitro": "latest", "react": "^19.1.1", "react-dom": "^19.1.1", "react-markdown": "^9.0.3", @@ -94,6 +96,46 @@ "vite": "^7.1.7", "vite-plugin-stylex": "^0.13.0", "vite-tsconfig-paths": "^5.1.4", + "vitest": "^4.0.16", + }, + }, + "web/packages/client": { + "name": "@inline/client", + "version": "0.0.0", + "dependencies": { + "@in/protocol": "workspace:*", + "@inline/config": "workspace:*", + "@inline/log": "workspace:*", + "react": "^19.1.1", + }, + "devDependencies": { + "@types/react": "^19.0.1", + "typescript": "^5.7.2", + "vitest": "^4.0.16", + }, + }, + "web/packages/config": { + "name": "@inline/config", + "version": "0.0.0", + "devDependencies": { + "typescript": "^5.7.2", + }, + }, + "web/packages/log": { + "name": "@inline/log", + "version": "0.0.0", + "devDependencies": { + "typescript": "^5.7.2", + }, + }, + "web/packages/protocol": { + "name": "@in/protocol", + "version": "0.0.0", + "dependencies": { + "@protobuf-ts/runtime": "^2.11.1", + }, + "devDependencies": { + "typescript": "^5.7.2", }, }, }, @@ -366,10 +408,18 @@ "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="], + "@in/protocol": ["@in/protocol@workspace:web/packages/protocol"], + "@inline-chat/server": ["@inline-chat/server@workspace:server"], "@inline-chat/web": ["@inline-chat/web@workspace:web"], + "@inline/client": ["@inline/client@workspace:web/packages/client"], + + "@inline/config": ["@inline/config@workspace:web/packages/config"], + + "@inline/log": ["@inline/log@workspace:web/packages/log"], + "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], @@ -816,12 +866,16 @@ "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], - "@types/bun": ["@types/bun@1.2.2", "", { "dependencies": { "bun-types": "1.2.2" } }, "sha512-tr74gdku+AEDN5ergNiBnplr7hpDp3V1h7fqI2GcR/rsUaM39jpSeKH0TFibRvU0KwniRx5POgaYnaXbk0hU+w=="], + "@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="], + + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + "@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="], "@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="], @@ -880,6 +934,20 @@ "@vitejs/plugin-react": ["@vitejs/plugin-react@5.0.4", "", { "dependencies": { "@babel/core": "^7.28.4", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.38", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-La0KD0vGkVkSk6K+piWDKRUyg8Rl5iAIKRMH0vMJI0Eg47bq1eOxmoObAaQG37WMW9MSyk7Cs8EIWwJC1PtzKA=="], + "@vitest/expect": ["@vitest/expect@4.0.16", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.16", "@vitest/utils": "4.0.16", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" } }, "sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA=="], + + "@vitest/mocker": ["@vitest/mocker@4.0.16", "", { "dependencies": { "@vitest/spy": "4.0.16", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@4.0.16", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-eNCYNsSty9xJKi/UdVD8Ou16alu7AYiS2fCPRs0b1OdhJiV89buAXQLpTbe+X8V9L6qrs9CqyvU7OaAopJYPsA=="], + + "@vitest/runner": ["@vitest/runner@4.0.16", "", { "dependencies": { "@vitest/utils": "4.0.16", "pathe": "^2.0.3" } }, "sha512-VWEDm5Wv9xEo80ctjORcTQRJ539EGPB3Pb9ApvVRAY1U/WkHXmmYISqU5E79uCwcW7xYUV38gwZD+RV755fu3Q=="], + + "@vitest/snapshot": ["@vitest/snapshot@4.0.16", "", { "dependencies": { "@vitest/pretty-format": "4.0.16", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-sf6NcrYhYBsSYefxnry+DR8n3UV4xWZwWxYbCJUt2YdvtqzSPR7VfGrY0zsv090DAbjFZsi7ZaMi1KnSRyK1XA=="], + + "@vitest/spy": ["@vitest/spy@4.0.16", "", {}, "sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw=="], + + "@vitest/utils": ["@vitest/utils@4.0.16", "", { "dependencies": { "@vitest/pretty-format": "4.0.16", "tinyrainbow": "^3.0.3" } }, "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA=="], + "abbrev": ["abbrev@3.0.0", "", {}, "sha512-+/kfrslGQ7TNV2ecmQwMJj/B65g5KVq1/L3SGVZ3tCYGqlzFuFCGBZJtMP99wH3NpEUyAjn0zPdPUg0D+DwrOA=="], "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], @@ -924,6 +992,8 @@ "assert-plus": ["assert-plus@1.0.0", "", {}, "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="], + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + "ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="], "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="], @@ -956,7 +1026,7 @@ "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], - "bun-types": ["bun-types@1.2.2", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-RCbMH5elr9gjgDGDhkTTugA21XtJAy/9jkKe/G3WR2q17VPGhcquf9Sir6uay9iW+7P/BV0CAHA1XlHXMAVKHg=="], + "bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="], "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="], @@ -970,6 +1040,8 @@ "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], + "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], @@ -1112,6 +1184,8 @@ "es-iterator-helpers": ["es-iterator-helpers@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.0.3", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.6", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.4", "safe-array-concat": "^1.1.3" } }, "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w=="], + "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], @@ -1150,10 +1224,14 @@ "estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="], + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], + "exsolve": ["exsolve@1.0.7", "", {}, "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw=="], "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], @@ -1456,7 +1534,7 @@ "lru-cache": ["lru-cache@10.2.2", "", {}, "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ=="], - "magic-string": ["magic-string@0.30.19", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="], + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], @@ -1612,6 +1690,8 @@ "object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="], + "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], + "ofetch": ["ofetch@2.0.0-alpha.3", "", {}, "sha512-zpYTCs2byOuft65vI3z43Dd6iSdFbOZZLb9/d21aCpx2rGastVU9dOCv0lu4ykc1Ur1anAYjDi3SUvR0vq50JA=="], "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], @@ -1792,6 +1872,8 @@ "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], "simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="], @@ -1808,6 +1890,10 @@ "srvx": ["srvx@0.9.6", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-5L4rT6qQqqb+xcoDoklUgCNdmzqJ6vbcDRwPVGRXewF55IJH0pqh0lQlrJ266ZWTKJ4mfeioqHQJeAYesS+RrQ=="], + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + + "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -1850,8 +1936,14 @@ "tiny-warning": ["tiny-warning@1.0.3", "", {}, "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="], + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + + "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + "tinyrainbow": ["tinyrainbow@3.0.3", "", {}, "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q=="], + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], @@ -1932,6 +2024,8 @@ "vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="], + "vitest": ["vitest@4.0.16", "", { "dependencies": { "@vitest/expect": "4.0.16", "@vitest/mocker": "4.0.16", "@vitest/pretty-format": "4.0.16", "@vitest/runner": "4.0.16", "@vitest/snapshot": "4.0.16", "@vitest/spy": "4.0.16", "@vitest/utils": "4.0.16", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.0.16", "@vitest/browser-preview": "4.0.16", "@vitest/browser-webdriverio": "4.0.16", "@vitest/ui": "4.0.16", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q=="], + "web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="], "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], @@ -1954,6 +2048,8 @@ "which-typed-array": ["which-typed-array@1.1.18", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA=="], + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], @@ -2048,6 +2144,8 @@ "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="], + "@inline-chat/server/@types/bun": ["@types/bun@1.2.2", "", { "dependencies": { "bun-types": "1.2.2" } }, "sha512-tr74gdku+AEDN5ergNiBnplr7hpDp3V1h7fqI2GcR/rsUaM39jpSeKH0TFibRvU0KwniRx5POgaYnaXbk0hU+w=="], + "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], "@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], @@ -2090,6 +2188,8 @@ "@tailwindcss/node/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + "@tailwindcss/node/magic-string": ["magic-string@0.30.19", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="], + "@tailwindcss/oxide/detect-libc": ["detect-libc@2.1.1", "", {}, "sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.5.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg=="], @@ -2164,6 +2264,10 @@ "@vitejs/plugin-react/@babel/core": ["@babel/core@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.4", "@babel/types": "^7.28.4", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA=="], + "@vitest/runner/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "@vitest/snapshot/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "apn/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], @@ -2182,6 +2286,8 @@ "elysia-rate-limit/debug": ["debug@4.3.4", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "estree-walker/@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], @@ -2234,6 +2340,8 @@ "vite-plugin-stylex/vite": ["vite@5.4.14", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA=="], + "vitest/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + "xmlbuilder2/js-yaml": ["js-yaml@3.14.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g=="], "@anthropic-ai/sdk/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], @@ -2320,6 +2428,8 @@ "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], + "@inline-chat/server/@types/bun/bun-types": ["bun-types@1.2.2", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-RCbMH5elr9gjgDGDhkTTugA21XtJAy/9jkKe/G3WR2q17VPGhcquf9Sir6uay9iW+7P/BV0CAHA1XlHXMAVKHg=="], + "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], diff --git a/desktop/assets/dev-app-icon-256.png b/desktop/assets/dev-app-icon-256.png new file mode 100644 index 000000000..b4fe6447f Binary files /dev/null and b/desktop/assets/dev-app-icon-256.png differ diff --git a/desktop/scripts/dev.ts b/desktop/scripts/dev.ts index 8ef064a39..e4368c0ad 100644 --- a/desktop/scripts/dev.ts +++ b/desktop/scripts/dev.ts @@ -14,7 +14,9 @@ const build = async () => { const run = () => { console.log("Running..."); - const proc = Bun.spawn(["bun", "electron", "build/index.js"]); + const proc = Bun.spawn(["bun", "electron", "build/index.js"], { + stdout: "inherit", + }); return () => { console.log("Killing..."); diff --git a/desktop/src/index.ts b/desktop/src/index.ts index 70a5888da..092fe641e 100644 --- a/desktop/src/index.ts +++ b/desktop/src/index.ts @@ -1,13 +1,20 @@ -import { app, BrowserWindow } from "electron"; +import { app, BrowserWindow, nativeImage, NativeImage } from "electron"; +import { isMacOS } from "./utils"; app.whenReady().then(() => { + // Set dev icon + if (isMacOS) { + let image = nativeImage.createFromPath("./assets/dev-app-icon-256.png"); + app.dock?.setIcon(image); + } + const mainWindow = new BrowserWindow({ width: 800, height: 600, transparent: true, trafficLightPosition: { - x: 14, - y: 14, + x: 16, + y: 16, }, frame: true, @@ -17,5 +24,6 @@ app.whenReady().then(() => { titleBarStyle: "hidden", vibrancy: "popover", }); + mainWindow.loadURL("http://localhost:8001/app"); }); diff --git a/desktop/src/utils.ts b/desktop/src/utils.ts new file mode 100644 index 000000000..18e469b0e --- /dev/null +++ b/desktop/src/utils.ts @@ -0,0 +1,3 @@ +import { platform } from "os"; +const p = platform(); +export const isMacOS = p == "darwin"; diff --git a/package.json b/package.json index 455855bcf..635f7b84e 100644 --- a/package.json +++ b/package.json @@ -4,18 +4,21 @@ "workspaces": [ "server", "web", + "web/packages/*", "scripts" ], "scripts": { "generate:proto": "cd scripts && bun run generate", "dev:server": "cd server && bun run dev", "dev:web": "cd web && bun run dev", + "dev:desktop": "cd desktop && bun run dev", "typecheck": "cd server && bun run typecheck", "dev": "cd server && bun run dev", "test": "cd server && bun test", "lint": "bunx oxlint@latest" }, "devDependencies": { + "@types/bun": "^1.3.5", "oxlint": "^0.16.0" }, "dependencies": { diff --git a/scripts/package.json b/scripts/package.json index dedbd09c0..2cd7ad566 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -3,8 +3,9 @@ "private": true, "scripts": { "proto:generate-ts": "npx protoc --plugin=./node_modules/.bin/protoc-gen-ts --ts_out=../server/packages/protocol/src --proto_path ../proto/ ../proto/core.proto ../proto/server.proto", + "proto:generate-web": "npx protoc --plugin=./node_modules/.bin/protoc-gen-ts --ts_out=../web/packages/protocol/src --proto_path ../proto/ ../proto/core.proto ../proto/client.proto", "proto:generate-swift": "npx protoc --swift_opt=Visibility=Public --swift_out ../apple/InlineKit/Sources/InlineProtocol/ --proto_path ../proto/ ../proto/core.proto ../proto/client.proto", - "generate": "bun run proto:generate-ts && bun run proto:generate-swift" + "generate": "bun run proto:generate-ts && bun run proto:generate-web && bun run proto:generate-swift" }, "dependencies": {}, "devDependencies": { diff --git a/server/src/libs/apn.ts b/server/src/libs/apn.ts index 0dcebbd24..93f0f3125 100644 --- a/server/src/libs/apn.ts +++ b/server/src/libs/apn.ts @@ -1,4 +1,5 @@ import APN from "apn" +import { Log } from "@in/server/utils/log" // Configure APN provider let apnProvider: APN.Provider | undefined @@ -9,14 +10,35 @@ export const getApnProvider = () => { } if (!apnProvider) { - apnProvider = new APN.Provider({ - token: { - key: Buffer.from(process.env["APN_KEY"] ?? "", "base64").toString("utf-8"), - keyId: process.env["APN_KEY_ID"] as string, - teamId: process.env["APN_TEAM_ID"] as string, - }, - production: process.env["NODE_ENV"] === "production", - }) + const rawKey = process.env["APN_KEY"] + const keyId = process.env["APN_KEY_ID"] + const teamId = process.env["APN_TEAM_ID"] + + if (!rawKey || !keyId || !teamId) { + Log.shared.warn("APN credentials are missing", { + hasKey: !!rawKey, + hasKeyId: !!keyId, + hasTeamId: !!teamId, + }) + return undefined + } + + const key = + rawKey.includes("BEGIN PRIVATE KEY") ? rawKey.replace(/\\n/g, "\n") : Buffer.from(rawKey, "base64").toString("utf-8") + + try { + apnProvider = new APN.Provider({ + token: { + key, + keyId, + teamId, + }, + production: process.env["NODE_ENV"] === "production", + }) + } catch (error) { + Log.shared.error("Failed to initialize APN provider", { error }) + return undefined + } } return apnProvider } diff --git a/server/src/libs/apnFailures.ts b/server/src/libs/apnFailures.ts index 8222246cd..44f98d338 100644 --- a/server/src/libs/apnFailures.ts +++ b/server/src/libs/apnFailures.ts @@ -1,6 +1,23 @@ type RecordValue = Record const isRecord = (value: unknown): value is RecordValue => typeof value === "object" && value !== null +const isError = (value: unknown): value is Error => + value instanceof Error || + (isRecord(value) && typeof value["message"] === "string" && typeof value["stack"] === "string") + +const extractErrorDetails = (value: unknown): { errorCode?: string; errorMessage?: string } => { + if (typeof value === "string") return { errorMessage: value } + if (isError(value)) { + const code = + isRecord(value) && typeof value["code"] === "string" ? (value["code"] as string) : undefined + return { errorCode: code, errorMessage: value.message } + } + if (!isRecord(value)) return {} + + const errorCode = typeof value["code"] === "string" ? value["code"] : undefined + const errorMessage = typeof value["message"] === "string" ? value["message"] : undefined + return { errorCode, errorMessage } +} export type ApnFailureSummary = { status?: number @@ -14,6 +31,15 @@ export type ApnFailureSummary = { const suppressedReasons = new Set(["BadDeviceToken", "Unregistered", "DeviceTokenNotForTopic", "TopicDisallowed"]) export const summarizeApnFailure = (failure: unknown): ApnFailureSummary => { + if (typeof failure === "string") { + return { errorMessage: failure } + } + + if (isError(failure)) { + const { errorCode, errorMessage } = extractErrorDetails(failure) + return { errorCode, errorMessage } + } + if (!isRecord(failure)) return {} const status = typeof failure["status"] === "number" ? failure["status"] : undefined @@ -23,24 +49,32 @@ export const summarizeApnFailure = (failure: unknown): ApnFailureSummary => { let errorCode: string | undefined let errorMessage: string | undefined + if (typeof failure["reason"] === "string") reason = failure["reason"] + const rawTimestamp = failure["timestamp"] + if (typeof rawTimestamp === "number") { + timestamp = rawTimestamp + } else if (typeof rawTimestamp === "string") { + const parsed = Number(rawTimestamp) + if (Number.isFinite(parsed)) timestamp = parsed + } + const response = failure["response"] if (isRecord(response)) { if (typeof response["reason"] === "string") reason = response["reason"] - const rawTimestamp = response["timestamp"] - if (typeof rawTimestamp === "number") { - timestamp = rawTimestamp - } else if (typeof rawTimestamp === "string") { - const parsed = Number(rawTimestamp) + const responseTimestamp = response["timestamp"] + if (typeof responseTimestamp === "number") { + timestamp = responseTimestamp + } else if (typeof responseTimestamp === "string") { + const parsed = Number(responseTimestamp) if (Number.isFinite(parsed)) timestamp = parsed } } const error = failure["error"] - if (isRecord(error)) { - if (!reason && typeof error["reason"] === "string") reason = error["reason"] - if (typeof error["code"] === "string") errorCode = error["code"] - if (typeof error["message"] === "string") errorMessage = error["message"] - } + if (isRecord(error) && !reason && typeof error["reason"] === "string") reason = error["reason"] + const extracted = extractErrorDetails(error) + if (extracted.errorCode) errorCode = extracted.errorCode + if (extracted.errorMessage) errorMessage = extracted.errorMessage return { status, reason, timestamp, errorCode, errorMessage } } @@ -56,4 +90,3 @@ export const shouldInvalidateTokenForApnFailure = (summary: ApnFailureSummary): if (!summary.reason) return false return suppressedReasons.has(summary.reason) } - diff --git a/web/package.json b/web/package.json index 45f51cfc0..d1d9be1c1 100644 --- a/web/package.json +++ b/web/package.json @@ -6,9 +6,12 @@ "dev": "vite dev", "build": "vite build", "start": "bun run .output/server/index.mjs", - "typecheck": "tsc" + "typecheck": "tsc", + "test": "vitest run", + "test:watch": "vitest" }, "dependencies": { + "@inline/client": "workspace:*", "@stylexjs/babel-plugin": "^0.16.0", "@stylexjs/postcss-plugin": "^0.16.0", "@stylexjs/stylex": "^0.16.0", @@ -20,7 +23,7 @@ "framer-motion": "^11.18.1", "isbot": "^5.1.17", "motion": "^12.23.22", - "nitro": "^3.0.1-alpha.1", + "nitro": "latest", "react": "^19.1.1", "react-dom": "^19.1.1", "react-markdown": "^9.0.3", @@ -36,6 +39,7 @@ "typescript": "^5.7.2", "vite": "^7.1.7", "vite-plugin-stylex": "^0.13.0", - "vite-tsconfig-paths": "^5.1.4" + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^4.0.16" } } diff --git a/web/packages/auth/package.json b/web/packages/auth/package.json new file mode 100644 index 000000000..cd7d9e944 --- /dev/null +++ b/web/packages/auth/package.json @@ -0,0 +1,20 @@ +{ + "name": "@inline/auth", + "private": true, + "version": "0.0.0", + "type": "module", + "types": "./src/index.ts", + "exports": { + ".": { + "types": "./src/index.ts", + "default": "./src/index.ts" + } + }, + "dependencies": { + "react": "^19.1.1" + }, + "devDependencies": { + "@types/react": "^19.0.1", + "typescript": "^5.7.2" + } +} diff --git a/web/packages/auth/src/core.ts b/web/packages/auth/src/core.ts new file mode 100644 index 000000000..47960d855 --- /dev/null +++ b/web/packages/auth/src/core.ts @@ -0,0 +1,247 @@ +type Listener = (value: T) => void + +type ChannelResolver = (result: IteratorResult) => void + +class AsyncChannel implements AsyncIterable { + private queue: T[] = [] + private resolvers: ChannelResolver[] = [] + private closed = false + + async send(value: T) { + if (this.closed) return + const resolver = this.resolvers.shift() + if (resolver) { + resolver({ value, done: false }) + return + } + this.queue.push(value) + } + + close() { + if (this.closed) return + this.closed = true + for (const resolver of this.resolvers) { + resolver({ value: undefined as T, done: true }) + } + this.resolvers = [] + this.queue = [] + } + + [Symbol.asyncIterator](): AsyncIterator { + return { + next: () => { + if (this.queue.length > 0) { + const value = this.queue.shift() as T + return Promise.resolve({ value, done: false }) + } + + if (this.closed) { + return Promise.resolve({ value: undefined as T, done: true }) + } + + return new Promise>((resolve) => { + this.resolvers.push(resolve) + }) + }, + } + } +} + +class Emitter { + private listeners = new Set>() + + emit(value: T) { + for (const listener of this.listeners) { + listener(value) + } + } + + subscribe(listener: Listener) { + this.listeners.add(listener) + return () => { + this.listeners.delete(listener) + } + } +} + +export type AuthSession = { + token: string + userId: number +} + +export type AuthState = { + token: string | null + currentUserId: number | null + hasHydrated: boolean +} + +export type AuthEvent = + | { type: "login"; session: AuthSession } + | { type: "logout" } + | { type: "update"; state: AuthState } + +export type AuthStoreOptions = { + storageKey?: string +} + +type PersistedAuthState = { + token?: string | null + currentUserId?: number | null + userId?: number | null +} + +const DEFAULT_STORAGE_KEY = "auth-store" + +const getStorage = () => { + if (typeof window === "undefined") return null + try { + return window.localStorage + } catch { + return null + } +} + +const parseUserId = (value: string | null) => { + if (value == null) return null + const trimmed = value.trim() + if (trimmed.length === 0) return null + const parsed = Number(trimmed) + if (!Number.isFinite(parsed)) return null + return parsed +} + +export class AuthStore { + readonly events = new AsyncChannel() + + private readonly emitter = new Emitter() + private readonly tokenStorageKey: string + private readonly userIdStorageKey: string + private readonly legacyStorageKey: string + private state: AuthState + + constructor(options?: AuthStoreOptions) { + const baseKey = options?.storageKey ?? DEFAULT_STORAGE_KEY + this.legacyStorageKey = baseKey + this.tokenStorageKey = `${baseKey}:token` + this.userIdStorageKey = `${baseKey}:user-id` + this.state = { + token: null, + currentUserId: null, + hasHydrated: false, + } + + this.hydrate() + } + + subscribe(listener: (state: AuthState) => void) { + return this.emitter.subscribe(listener) + } + + getSnapshot = () => this.state + + getState() { + return this.state + } + + isLoggedIn() { + return this.state.token != null && this.state.currentUserId != null + } + + getToken() { + return this.state.token + } + + login(session: AuthSession) { + this.state = { + ...this.state, + token: session.token, + currentUserId: session.userId, + } + this.persist() + this.emit({ type: "login", session }) + } + + logout() { + if (this.state.token == null && this.state.currentUserId == null) return + this.state = { + ...this.state, + token: null, + currentUserId: null, + } + this.persist() + this.emit({ type: "logout" }) + } + + private hydrate() { + const storage = getStorage() + if (!storage) { + this.state = { ...this.state, hasHydrated: true } + this.emit({ type: "update", state: this.state }) + return + } + + let token = storage.getItem(this.tokenStorageKey) + token = token && token.length > 0 ? token : null + + let currentUserId = parseUserId(storage.getItem(this.userIdStorageKey)) + + if (!token && currentUserId == null) { + const legacy = storage.getItem(this.legacyStorageKey) + if (legacy) { + try { + const parsed = JSON.parse(legacy) as PersistedAuthState + token = parsed.token ?? null + const legacyUserId = parsed.currentUserId ?? parsed.userId ?? null + currentUserId = typeof legacyUserId === "number" && Number.isFinite(legacyUserId) ? legacyUserId : null + + if (token) { + storage.setItem(this.tokenStorageKey, token) + } + if (currentUserId != null) { + storage.setItem(this.userIdStorageKey, String(currentUserId)) + } + + storage.removeItem(this.legacyStorageKey) + } catch { + // ignore malformed storage entries + } + } + } + + this.state = { + ...this.state, + token: token ?? null, + currentUserId: currentUserId ?? null, + hasHydrated: true, + } + this.emit({ type: "update", state: this.state }) + } + + private persist() { + const storage = getStorage() + if (!storage) return + + try { + if (this.state.token) { + storage.setItem(this.tokenStorageKey, this.state.token) + } else { + storage.removeItem(this.tokenStorageKey) + } + + if (this.state.currentUserId != null) { + storage.setItem(this.userIdStorageKey, String(this.state.currentUserId)) + } else { + storage.removeItem(this.userIdStorageKey) + } + } catch { + // ignore storage errors + } + } + + private emit(event: AuthEvent) { + this.emitter.emit(this.state) + void this.events.send(event) + } +} + +export const auth = new AuthStore() diff --git a/web/packages/auth/src/index.ts b/web/packages/auth/src/index.ts new file mode 100644 index 000000000..bc9b9f97c --- /dev/null +++ b/web/packages/auth/src/index.ts @@ -0,0 +1,2 @@ +export * from "./core" +export * from "./react" diff --git a/web/packages/auth/src/react.tsx b/web/packages/auth/src/react.tsx new file mode 100644 index 000000000..eb0774302 --- /dev/null +++ b/web/packages/auth/src/react.tsx @@ -0,0 +1,40 @@ +import { useSyncExternalStore } from "react" +import type { AuthSession, AuthState, AuthStore } from "./core" + +export function useAuthState(auth: AuthStore): AuthState { + return useSyncExternalStore( + (listener) => auth.subscribe(() => listener()), + () => auth.getSnapshot(), + () => auth.getSnapshot(), + ) +} + +export function useIsLoggedIn(auth: AuthStore): boolean { + return useSyncExternalStore( + (listener) => auth.subscribe(() => listener()), + () => auth.isLoggedIn(), + () => auth.isLoggedIn(), + ) +} + +export function useToken(auth: AuthStore): string | null { + const state = useAuthState(auth) + return state.token +} + +export function useCurrentUserId(auth: AuthStore): number | null { + const state = useAuthState(auth) + return state.currentUserId +} + +export function useHasHydrated(auth: AuthStore): boolean { + const state = useAuthState(auth) + return state.hasHydrated +} + +export function useAuthActions(auth: AuthStore) { + return { + login: (session: AuthSession) => auth.login(session), + logout: () => auth.logout(), + } +} diff --git a/web/packages/auth/tsconfig.json b/web/packages/auth/tsconfig.json new file mode 100644 index 000000000..166c62fed --- /dev/null +++ b/web/packages/auth/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "composite": true, + "rootDir": ".", + "noUncheckedIndexedAccess": false + }, + "include": ["src"] +} diff --git a/web/packages/client/README.md b/web/packages/client/README.md new file mode 100644 index 000000000..2dec3b876 --- /dev/null +++ b/web/packages/client/README.md @@ -0,0 +1,12 @@ +# @inline/client + +Goals: +- Own client-side auth state, local database cache, and realtime protocol wiring. +- Provide a small, typed API for queries/transactions and connection state. +- Offer React bindings for app integration (context + hooks). + +Modules: +- `auth/`: persisted auth state + client info, emits login/logout updates. +- `database/`: in-memory object cache with query/subscription helpers. +- `realtime/`: protocol client, transport, transactions, and connection state. +- `react/`: React context + hooks for auth, db, realtime, and connection state. diff --git a/web/packages/client/package.json b/web/packages/client/package.json new file mode 100644 index 000000000..330d12771 --- /dev/null +++ b/web/packages/client/package.json @@ -0,0 +1,30 @@ +{ + "name": "@inline/client", + "private": true, + "version": "0.0.0", + "type": "module", + "types": "./src/index.ts", + "exports": { + ".": { + "types": "./src/index.ts", + "default": "./src/index.ts" + } + }, + "scripts": { + "test": "vitest run", + "test:watch": "vitest", + "typecheck": "tsc -p tsconfig.json" + }, + "dependencies": { + "@inline/auth": "workspace:*", + "react": "^19.1.1", + "@in/protocol": "workspace:*", + "@inline/config": "workspace:*", + "@inline/log": "workspace:*" + }, + "devDependencies": { + "@types/react": "^19.0.1", + "typescript": "^5.7.2", + "vitest": "^4.0.16" + } +} diff --git a/web/packages/client/src/auth/auth-store.ts b/web/packages/client/src/auth/auth-store.ts new file mode 100644 index 000000000..b2a9d2261 --- /dev/null +++ b/web/packages/client/src/auth/auth-store.ts @@ -0,0 +1,2 @@ +export { AuthStore } from "@inline/auth" +export type { AuthEvent, AuthSession, AuthState, AuthStoreOptions } from "@inline/auth" diff --git a/web/packages/client/src/auth/index.ts b/web/packages/client/src/auth/index.ts new file mode 100644 index 000000000..d49d3dcc8 --- /dev/null +++ b/web/packages/client/src/auth/index.ts @@ -0,0 +1,2 @@ +export { auth, AuthStore } from "@inline/auth" +export type { AuthEvent, AuthSession, AuthState, AuthStoreOptions } from "@inline/auth" diff --git a/web/packages/client/src/database/index.test.ts b/web/packages/client/src/database/index.test.ts new file mode 100644 index 000000000..1bb451726 --- /dev/null +++ b/web/packages/client/src/database/index.test.ts @@ -0,0 +1,216 @@ +import { describe, it, expect, beforeEach, vi } from "vitest" +import { Db } from "./index" +import { DbObjectKind, type User, type Message } from "./models" +import { DbQueryPlanType } from "./types" + +describe("Database", () => { + let db: Db + + beforeEach(() => { + db = new Db() + }) + + // 1. Insert and get + it("should insert and retrieve an object", () => { + const user: User = { kind: DbObjectKind.User, id: 1, firstName: "Alice" } + db.insert(user) + + const ref = db.ref(DbObjectKind.User, 1) + const retrieved = db.get(ref) + + expect(retrieved).toEqual(user) + }) + + // 2. Update + it("should update an existing object", () => { + const user: User = { kind: DbObjectKind.User, id: 1, firstName: "Alice" } + db.insert(user) + + const updated: User = { kind: DbObjectKind.User, id: 1, firstName: "Bob" } + db.update(updated) + + const ref = db.ref(DbObjectKind.User, 1) + const retrieved = db.get(ref) + + expect(retrieved?.firstName).toBe("Bob") + }) + + it("should merge updates without clearing existing fields", () => { + const user: User = { kind: DbObjectKind.User, id: 1, firstName: "Alice", lastName: "Smith" } + db.insert(user) + + const updated: User = { kind: DbObjectKind.User, id: 1, firstName: "Bob" } + db.update(updated) + + const ref = db.ref(DbObjectKind.User, 1) + const retrieved = db.get(ref) + + expect(retrieved?.firstName).toBe("Bob") + expect(retrieved?.lastName).toBe("Smith") + }) + + it("should replace on insert when object already exists", () => { + const user: User = { kind: DbObjectKind.User, id: 1, firstName: "Alice", lastName: "Smith" } + db.insert(user) + + const replacement: User = { kind: DbObjectKind.User, id: 1, lastName: "Jones" } + db.insert(replacement) + + const ref = db.ref(DbObjectKind.User, 1) + const retrieved = db.get(ref) + + expect(retrieved?.firstName).toBeUndefined() + expect(retrieved?.lastName).toBe("Jones") + }) + + // 3. Delete + it("should delete an object", () => { + const user: User = { kind: DbObjectKind.User, id: 1, firstName: "Alice" } + db.insert(user) + + const ref = db.ref(DbObjectKind.User, 1) + db.delete(ref) + + const retrieved = db.get(ref) + expect(retrieved).toBeUndefined() + }) + + // 4. Ref stability + it("should return stable refs for the same id", () => { + const user: User = { kind: DbObjectKind.User, id: 1, firstName: "Alice" } + db.insert(user) + + const ref1 = db.ref(DbObjectKind.User, 1) + const ref2 = db.ref(DbObjectKind.User, 1) + + expect(ref1).toBe(ref2) // Same object reference + }) + + // 5. Object subscription + it("should notify object subscribers on update", () => { + const user: User = { kind: DbObjectKind.User, id: 1, firstName: "Alice" } + db.insert(user) + + const ref = db.ref(DbObjectKind.User, 1) + const callback = vi.fn() + + db.subscribeToObject(ref, callback) + + const updated: User = { kind: DbObjectKind.User, id: 1, firstName: "Bob" } + db.update(updated) + + expect(callback).toHaveBeenCalledTimes(1) + }) + + // 6. Object subscription unsubscribe + it("should stop notifying after unsubscribe", () => { + const user: User = { kind: DbObjectKind.User, id: 1, firstName: "Alice" } + db.insert(user) + + const ref = db.ref(DbObjectKind.User, 1) + const callback = vi.fn() + + const { unsubscribe } = db.subscribeToObject(ref, callback) + unsubscribe() + + const updated: User = { kind: DbObjectKind.User, id: 1, firstName: "Bob" } + db.update(updated) + + expect(callback).not.toHaveBeenCalled() + }) + + // 7. Query collection + it("should query objects with predicate", () => { + const msg1: Message = { kind: DbObjectKind.Message, id: 1, fromId: 1, chatId: 100, message: "Hello" } + const msg2: Message = { kind: DbObjectKind.Message, id: 2, fromId: 1, chatId: 200, message: "World" } + const msg3: Message = { kind: DbObjectKind.Message, id: 3, fromId: 2, chatId: 100, message: "Hi" } + + db.insert(msg1) + db.insert(msg2) + db.insert(msg3) + + const chat100Messages = db.queryCollection( + DbQueryPlanType.Objects, + DbObjectKind.Message, + (m: Message) => m.chatId === 100, + ) + + expect(chat100Messages).toHaveLength(2) + expect(chat100Messages.map((m: Message) => m.id).sort()).toEqual([1, 3]) + }) + + // 8. Query caching + it("should cache query results", () => { + const msg: Message = { kind: DbObjectKind.Message, id: 1, fromId: 1, chatId: 100, message: "Hello" } + db.insert(msg) + + const predicate = (m: Message) => m.chatId === 100 + const key = "test-query" + + // Subscribe to register the query + db.subscribeToQuery(key, DbQueryPlanType.Objects, DbObjectKind.Message, predicate, () => {}) + + // First call computes + const result1 = db.queryCached(key, DbQueryPlanType.Objects, DbObjectKind.Message, predicate) + // Second call should return cached + const result2 = db.queryCached(key, DbQueryPlanType.Objects, DbObjectKind.Message, predicate) + + expect(result1).toBe(result2) // Same array reference + }) + + // 9. Batch operations + it("should batch notifications", () => { + const callback = vi.fn() + const predicate = () => true + const key = "batch-test" + + db.subscribeToQuery(key, DbQueryPlanType.Objects, DbObjectKind.Message, predicate, callback) + // Clear the initial dirty state + db.queryCached(key, DbQueryPlanType.Objects, DbObjectKind.Message, predicate) + callback.mockClear() + + db.batch(() => { + for (let i = 0; i < 10; i++) { + const msg: Message = { kind: DbObjectKind.Message, id: i, fromId: 1, chatId: 100, message: `Msg ${i}` } + db.insert(msg) + } + }) + + // Should only notify once, not 10 times + expect(callback).toHaveBeenCalledTimes(1) + }) + + // 10. Nested batch operations + it("should handle nested batches correctly", () => { + const callback = vi.fn() + const predicate = () => true + const key = "nested-batch-test" + + db.subscribeToQuery(key, DbQueryPlanType.Objects, DbObjectKind.Message, predicate, callback) + db.queryCached(key, DbQueryPlanType.Objects, DbObjectKind.Message, predicate) + callback.mockClear() + + db.batch(() => { + const msg1: Message = { kind: DbObjectKind.Message, id: 1, fromId: 1, chatId: 100, message: "Outer 1" } + db.insert(msg1) + + db.batch(() => { + const msg2: Message = { kind: DbObjectKind.Message, id: 2, fromId: 1, chatId: 100, message: "Inner" } + db.insert(msg2) + }) + + // Inner batch should not trigger yet + expect(callback).not.toHaveBeenCalled() + + const msg3: Message = { kind: DbObjectKind.Message, id: 3, fromId: 1, chatId: 100, message: "Outer 2" } + db.insert(msg3) + }) + + // All notifications should fire once at the end + expect(callback).toHaveBeenCalledTimes(1) + + // All messages should be inserted + const messages = db.queryCollection(DbQueryPlanType.Objects, DbObjectKind.Message, () => true) + expect(messages).toHaveLength(3) + }) +}) diff --git a/web/packages/client/src/database/index.ts b/web/packages/client/src/database/index.ts new file mode 100644 index 000000000..c10fadf20 --- /dev/null +++ b/web/packages/client/src/database/index.ts @@ -0,0 +1,494 @@ +import { DbModels, DbObjectKind } from "./models" +import { CollectionStorage, createCollectionStorage } from "./storage" +import { DbObjectRef, DbQueryPlan, DbQueryPlanType } from "./types" + +type DbStorageByKind = Partial<{ + [K in DbObjectKind]: CollectionStorage | null +}> + +type DbOptions = { + storageByKind?: DbStorageByKind + autoHydrate?: boolean +} + +export class Db { + // node persistence layer + // hydrate into buckets and form indexes + // create ref types + // create query hooks ( that give a light evaluation function for an object and we loop and fetch it ) + // ----- ^ we'll use this function to evaluate if a query needs to re-run on addition/removal of objects WOW. + // a helper to generate refs from raw IDs + // create object hooks to go from ref -> object + // keep a list of subscriptions for objects (for updates) and queries (addition/removal) to trigger + // goal: 1) to create a lightweight, object-based, simple, reactive cache layer that plays well with React 2) easily expandable/modular for later upgrades to every layer (persistence, hooks, queries, schema, etc) + // we need stable refs. + // TODO: Maybe we need to insert a private symbol in refs to ensure they come from us and are stable. + + collections: Partial>> = {} + querySubscriptions = new Queries() + objectSubscriptions = new ObjectSubscriptions() + ready: Promise + hasHydrated = false + hydrationState: "pending" | "skipped" | "done" | "failed" + + // Batching + private batchDepth = 0 + private pendingRefs: Set> = new Set() + private hydrationPromise: Promise | null = null + private storageByKind?: DbStorageByKind + + constructor(options: DbOptions = {}) { + const autoHydrate = options.autoHydrate ?? true + this.storageByKind = options.storageByKind + this.hasHydrated = !autoHydrate + this.hydrationState = autoHydrate ? "pending" : "skipped" + this.ready = autoHydrate ? this.hydrate() : Promise.resolve() + } + + insert(object: O) { + this.collection(object.kind).insert(object) + this.notify(this.ref(object.kind, object.id)) + } + + delete(ref: DbObjectRef) { + this.collection(ref.kind).delete(ref.id) + this.notify(ref) + } + + update(object: O) { + this.collection(object.kind).update(object) + this.notify(this.ref(object.kind, object.id)) + } + + /** Batch multiple operations, deferring notifications until the batch completes. */ + batch(fn: () => void): void { + this.batchDepth++ + try { + fn() + } finally { + this.batchDepth-- + if (this.batchDepth === 0) { + this.flushPendingNotifications() + } + } + } + + private notify(ref: DbObjectRef) { + if (this.batchDepth > 0) { + this.pendingRefs.add(ref) + } else { + this.triggerQueries(ref) + this.triggerObjectSubscriptions(ref) + } + } + + private flushPendingNotifications() { + if (this.pendingRefs.size === 0) return + + // Collect affected kinds for query invalidation + let affectedKinds = new Set() + for (const ref of this.pendingRefs) { + affectedKinds.add(ref.kind) + this.triggerObjectSubscriptions(ref) + } + + // Invalidate and notify queries once per kind + for (const kind of affectedKinds) { + this.querySubscriptions.markKindDirty(kind) + let queriesForKind = this.querySubscriptions.getQueriesByKind(kind) + for (const query of queriesForKind) { + let callbacks = this.querySubscriptions.getCallbacksForKey(query.key) + for (let callback of callbacks) { + callback() + } + } + } + + this.pendingRefs.clear() + } + + get(ref: DbObjectRef): O | undefined { + return this.collection(ref.kind).get(ref.id) as O | undefined + } + + ref(kind: K, id: number): DbObjectRef { + return this.collection(kind).ref(id) + } + + subscribeToObject(ref: DbObjectRef, callback: () => void): { unsubscribe: () => void } { + let { unsubscribe } = this.objectSubscriptions.subscribe(ref, callback) + return { unsubscribe } + } + + private triggerObjectSubscriptions(ref: DbObjectRef) { + let subscriptions = this.objectSubscriptions.getSubscriptionsForRef(ref) + for (const subscription of subscriptions) { + subscription() + } + } + + subscribeToQuery( + key: string, + type: DbQueryPlanType, + kind: K, + predicate: (object: O) => boolean, + callback: () => void, + ): { unsubscribe: () => void } { + let queryPlan: DbQueryPlan = { key, type, kind, predicate } + let { unsubscribe } = this.querySubscriptions.subscribe(queryPlan, callback) + return { unsubscribe } + } + + private triggerQueries(ref: DbObjectRef) { + // Mark all queries for this kind as dirty + // Future: check predicates here to skip unaffected queries + this.querySubscriptions.markKindDirty(ref.kind) + + // Notify subscribers + let queriesForKind = this.querySubscriptions.getQueriesByKind(ref.kind) + for (const query of queriesForKind) { + let callbacks = this.querySubscriptions.getCallbacksForKey(query.key) + for (let callback of callbacks) { + callback() + } + } + } + + queryCollection( + type: T, + kind: K, + predicate: (object: O) => boolean = () => true, + ): T extends DbQueryPlanType.Objects ? O[] : DbObjectRef[] { + if (type === DbQueryPlanType.Objects) { + return this.collection(kind).getAll(predicate) as T extends DbQueryPlanType.Objects ? O[] : never + } else { + return this.collection(kind).getAllRefs(predicate) as T extends DbQueryPlanType.Objects + ? never + : DbObjectRef[] + } + } + + /** Cached version of queryCollection. Returns cached result if valid, otherwise computes fresh. */ + queryCached( + key: string, + type: T, + kind: K, + predicate: (object: O) => boolean = () => true, + ): T extends DbQueryPlanType.Objects ? O[] : DbObjectRef[] { + if (!this.querySubscriptions.isDirty(key)) { + return this.querySubscriptions.getCachedResult(key)! + } + const result = this.queryCollection(type, kind, predicate) + this.querySubscriptions.setCachedResult(key, result) + return result + } + + async hydrate(): Promise { + if (this.hydrationPromise) return this.hydrationPromise + + this.hydrationPromise = (async () => { + if (this.hydrationState === "skipped") { + this.hydrationState = "pending" + } + try { + const kinds = Object.values(DbObjectKind) as DbObjectKind[] + const results = await Promise.all( + kinds.map(async (kind) => { + const collection = this.collection(kind) + const objects = await collection.hydrate() + return { kind, objects } + }), + ) + + this.batch(() => { + for (const { kind, objects } of results) { + for (const object of objects) { + this.notify(this.ref(kind, object.id)) + } + } + }) + this.hydrationState = "done" + } catch { + this.hydrationState = "failed" + return + } finally { + this.hasHydrated = true + } + })() + + return this.hydrationPromise + } + + // Private + private collection(kind: K): Collection { + if (!this.collections[kind]) { + const storage = this.storageByKind?.[kind] ?? createCollectionStorage(kind) + this.collections[kind] = new Collection(kind, storage) + } + return this.collections[kind] as Collection + } +} + +class Collection { + kind: K + ids: Set = new Set() + objectsById: Map = new Map() + // stable refs by ID + refs: Map> = new Map() + hasHydrated = false + private hydrationPromise: Promise | null = null + private storage: CollectionStorage | null + + constructor(kind: K, storage: CollectionStorage | null) { + this.kind = kind + this.storage = storage + } + + ref(id: number): DbObjectRef { + let ref = this.refs.get(id) + if (!ref) { + ref = { kind: this.kind, id } as DbObjectRef + this.refs.set(id, ref) + } + return ref + } + + insert(object: O) { + this.insertLocal(object) + this.persistPut(object) + } + + private insertLocal(object: O) { + this.ids.add(object.id) + // Insert is a replace if the object already exists. + this.objectsById.set(object.id, object) + } + + delete(id: number) { + this.ids.delete(id) + this.objectsById.delete(id) + this.persistDelete(id) + } + + update(object: O): O { + const merged = this.updateLocal(object) + this.persistPut(merged) + return merged + } + + private updateLocal(object: O): O { + const existing = this.objectsById.get(object.id) + if (!existing) { + this.insertLocal(object) + return object + } + + const merged = { ...existing } + for (const [key, value] of Object.entries(object) as [keyof O, O[keyof O]][]) { + if (value !== undefined) { + merged[key] = value + } + } + + this.objectsById.set(object.id, merged) + return merged + } + + get(id: number): O | undefined { + return this.objectsById.get(id) + } + + getAll(predicate: (object: O) => boolean = () => true): O[] { + // optimize memory??? + let objects: O[] = [] + for (const id of this.ids) { + const object = this.objectsById.get(id) + if (object && predicate(object)) { + objects.push(object) + } + } + return objects + } + + getAllRefs(predicate: (object: O) => boolean = () => true): DbObjectRef[] { + let refs: DbObjectRef[] = [] + for (const id of this.ids) { + const object = this.objectsById.get(id) + if (object && predicate(object)) { + refs.push(this.ref(id)) + } + } + return refs + } + + async hydrate(): Promise { + if (!this.storage) { + console.error("No storage for collection", this.kind) + this.hasHydrated = true + return [] + } + if (this.hydrationPromise) return this.hydrationPromise + + this.hydrationPromise = (async () => { + try { + await this.storage!.init() + const objects = await this.storage!.getAll() + const inserted: O[] = [] + for (const object of objects) { + if (this.objectsById.has(object.id)) continue + this.insertLocal(object) + inserted.push(object) + } + return inserted + } catch { + return [] + } finally { + this.hasHydrated = true + } + })() + + return this.hydrationPromise + } + + private persistPut(object: O) { + if (!this.storage) return + void this.storage.put(object).catch(() => {}) + } + + private persistDelete(id: number) { + if (!this.storage) return + void this.storage.delete(id).catch(() => {}) + } +} + +type QueryKey = string + +class Queries { + // Current state + private queries: Map> = new Map() + private queriesByKind: Map> = new Map() + private subscriptions: Map void>> = new Map() + + // Cache + private cachedResults: Map = new Map() + private dirtyQueries: Set = new Set() + + subscribe(query: DbQueryPlan, callback: () => void) { + this.queries.set(query.key, query as unknown as DbQueryPlan) + this.queriesByKindSet(query.kind).add(query.key) + this.subscriptionSet(query.key).add(callback) + this.dirtyQueries.add(query.key) // Needs initial compute + + // Unsubscribe + return { + unsubscribe: () => { + this.subscriptions.get(query.key)?.delete(callback) + + // Do this with a delay to avoid immediate deletion of the query if the callback is re-added immediately + // TODO: create a garbage collection mechanism for house keeping instead of setTimeout on every unsubscribe + setTimeout(() => { + this.maybeDeleteQueryIfNoSubscriptions(query.key) + }, 50) + }, + } + } + + getQueriesByKind>(kind: K): P[] { + let queryKeys = this.queriesByKind.get(kind) + if (!queryKeys) return [] + let queries: P[] = [] + for (const queryKey of queryKeys) { + let query = this.queries.get(queryKey) as P | undefined + if (!query) continue + queries.push(query) + } + return queries + } + + getCallbacksForKey(key: QueryKey): (() => void)[] { + return Array.from(this.subscriptions.get(key) ?? new Set()) + } + + // Cache methods + + /** Mark all queries for a kind as dirty. Future: check predicates here. */ + markKindDirty(kind: DbObjectKind) { + let queryKeys = this.queriesByKind.get(kind) + if (!queryKeys) return + for (const key of queryKeys) { + this.dirtyQueries.add(key) + } + } + + isDirty(key: QueryKey): boolean { + return this.dirtyQueries.has(key) || !this.cachedResults.has(key) + } + + getCachedResult(key: QueryKey): T | undefined { + return this.cachedResults.get(key) as T | undefined + } + + setCachedResult(key: QueryKey, result: unknown) { + this.cachedResults.set(key, result) + this.dirtyQueries.delete(key) + } + + // Private + private queriesByKindSet(kind: DbObjectKind): Set { + if (!this.queriesByKind.has(kind)) { + this.queriesByKind.set(kind, new Set()) + } + return this.queriesByKind.get(kind) as Set + } + + private subscriptionSet(key: QueryKey): Set<() => void> { + if (!this.subscriptions.has(key)) { + this.subscriptions.set(key, new Set()) + } + return this.subscriptions.get(key)! + } + + private deleteQuery(key: QueryKey) { + let query = this.queries.get(key) as DbQueryPlan | undefined + if (!query) return + this.queries.delete(key) + this.queriesByKindSet(query.kind).delete(key) + this.cachedResults.delete(key) + this.dirtyQueries.delete(key) + } + + private maybeDeleteQueryIfNoSubscriptions(key: QueryKey) { + if (this.subscriptions.get(key)?.size === 0) { + this.deleteQuery(key) + } + } +} + +// TODO: Improve type-safety of object here + +class ObjectSubscriptions { + private subscriptions: Map, Set<() => void>> = new Map() + + subscribe(ref: DbObjectRef, callback: () => void) { + this.subscriptionSet(ref).add(callback) + + return { + unsubscribe: () => { + this.subscriptionSet(ref).delete(callback) + }, + } + } + + getSubscriptionsForRef(ref: DbObjectRef): Set<() => void> { + return this.subscriptions.get(ref) ?? new Set() + } + + private subscriptionSet(ref: DbObjectRef): Set<() => void> { + if (!this.subscriptions.has(ref)) { + this.subscriptions.set(ref, new Set()) + } + return this.subscriptions.get(ref)! + } +} + +export let db = new Db() diff --git a/web/packages/client/src/database/models.ts b/web/packages/client/src/database/models.ts new file mode 100644 index 000000000..e5e648b4a --- /dev/null +++ b/web/packages/client/src/database/models.ts @@ -0,0 +1,110 @@ +/** + * Models + * + * We use plain typescript objects for now. Later we can upgrade to Effect Schema or Zod. + * + */ + +export type DbModel = User | Dialog | Chat | Message + +export enum DbObjectKind { + Dialog = "D", + Chat = "C", + User = "U", + Message = "M", + Space = "S", +} + +export type DbModels = { + [DbObjectKind.User]: User + [DbObjectKind.Dialog]: Dialog + [DbObjectKind.Chat]: Chat + [DbObjectKind.Message]: Message + [DbObjectKind.Space]: Space +} + +export interface DbModelBase { + kind: K + id: number +} + +export interface User extends DbModelBase { + kind: DbObjectKind.User + id: number + firstName?: string + lastName?: string + username?: string + email?: string + min?: boolean + + profilePhoto?: { + fileUniqueId?: string + /** not filled out */ + photoId?: number + cdnUrl?: string + } +} + +export interface Dialog extends DbModelBase { + kind: DbObjectKind.Dialog + // driven from associated chat ID + id: number + chatId: number + peerUserId?: number + spaceId?: number + archived?: boolean + pinned?: boolean + readMaxId?: number + unreadCount?: number + unreadMark?: boolean +} + +export interface Chat extends DbModelBase { + kind: DbObjectKind.Chat + id: number + title?: string + spaceId?: number + emoji?: string + isPublic?: boolean + lastMsgId?: number +} + +export interface Message extends DbModelBase { + kind: DbObjectKind.Message + id: number + randomId?: bigint + fromId: number + peerUserId?: number + chatId: number + message?: string + out?: boolean + date?: number + mentioned?: boolean + replyToMsgId?: number + editDate?: number + isSticker?: boolean +} + +// export interface Photo extends DbModelBase { +// kind: DbObjectKind.Photo +// id: number +// chatId: number +// messageId: number +// photoId: number +// photo: Photo +// } + +export interface Space extends DbModelBase { + kind: DbObjectKind.Space + + id: number + + // Name of the space + name: string + + // Whether the current user is the creator of the space + creator: boolean + + // Date of creation + date: number +} diff --git a/web/packages/client/src/database/react.tsx b/web/packages/client/src/database/react.tsx new file mode 100644 index 000000000..19c7c10f5 --- /dev/null +++ b/web/packages/client/src/database/react.tsx @@ -0,0 +1,76 @@ +import { useCallback, useMemo, useSyncExternalStore } from "react" +import { db } from "./index" +import { DbModels, DbObjectKind } from "./models" +import { DbObjectRef, DbQueryPlanType } from "./types" + +const defaultPredicate = () => true + +/** Get a stable ref for a given kind and id. Returns undefined if id is undefined. */ +export function useObjectRef(kind: K, id: number | undefined): DbObjectRef | undefined { + return useMemo(() => (id !== undefined ? db.ref(kind, id) : undefined), [kind, id]) +} + +/** Subscribe to a single object by ref. Returns the object or undefined if not found. */ +export function useObject( + ref: DbObjectRef | undefined, +): O | undefined { + const subscribe = useCallback( + (onStoreChange: () => void) => { + if (!ref) return () => {} + const { unsubscribe } = db.subscribeToObject(ref, onStoreChange) + return unsubscribe + }, + [ref], + ) + + const getSnapshot = useCallback(() => { + if (!ref) return undefined + return db.get(ref) + }, [ref]) + + return useSyncExternalStore(subscribe, getSnapshot, getSnapshot) +} + +/** Subscribe to a query that returns objects. */ +export function useQueryObjects( + kind: K, + predicate: (object: O) => boolean = defaultPredicate, +): O[] { + const key = useMemo(() => `query:${kind}:objects:${predicate.toString()}`, [kind, predicate]) + + const subscribe = useCallback( + (onStoreChange: () => void) => { + const { unsubscribe } = db.subscribeToQuery(key, DbQueryPlanType.Objects, kind, predicate, onStoreChange) + return unsubscribe + }, + [key, kind], + ) + + const getSnapshot = useCallback(() => { + return db.queryCached(key, DbQueryPlanType.Objects, kind, predicate) + }, [key, kind, predicate]) + + return useSyncExternalStore(subscribe, getSnapshot, getSnapshot) +} + +/** Subscribe to a query that returns refs. */ +export function useQueryRefs( + kind: K, + predicate: (object: O) => boolean = defaultPredicate, +): DbObjectRef[] { + const key = useMemo(() => `query:${kind}:refs:${predicate.toString()}`, [kind, predicate]) + + const subscribe = useCallback( + (onStoreChange: () => void) => { + const { unsubscribe } = db.subscribeToQuery(key, DbQueryPlanType.Refs, kind, predicate, onStoreChange) + return unsubscribe + }, + [key, kind, predicate], + ) + + const getSnapshot = useCallback(() => { + return db.queryCached(key, DbQueryPlanType.Refs, kind, predicate) + }, [key, kind, predicate]) + + return useSyncExternalStore(subscribe, getSnapshot, getSnapshot) +} diff --git a/web/packages/client/src/database/storage.ts b/web/packages/client/src/database/storage.ts new file mode 100644 index 000000000..b54bc73bb --- /dev/null +++ b/web/packages/client/src/database/storage.ts @@ -0,0 +1,108 @@ +import { DbModels, DbObjectKind } from "./models" + +type CollectionStorage = { + init: () => Promise + get: (id: number) => Promise + getAll: () => Promise + put: (object: O) => Promise + delete: (id: number) => Promise +} + +const DEFAULT_DB_NAME = "inline-client-db" +const DEFAULT_STORE_NAME = "objects" +const DEFAULT_KIND_INDEX = "kind" + +class IndexedDbStorage implements CollectionStorage { + private dbPromise: Promise | null = null + + constructor( + private kind: K, + private dbName: string = DEFAULT_DB_NAME, + private storeName: string = DEFAULT_STORE_NAME, + private kindIndex: string = DEFAULT_KIND_INDEX, + ) {} + + async init(): Promise { + await this.open() + } + + async get(id: number): Promise { + return this.read((store) => store.get([this.kind, id])) as Promise + } + + async getAll(): Promise { + return this.read((store) => store.index(this.kindIndex).getAll(this.kind)) as Promise + } + + async put(object: O): Promise { + await this.write((store) => { + store.put(object) + }) + } + + async delete(id: number): Promise { + await this.write((store) => { + store.delete([this.kind, id]) + }) + } + + private open(): Promise { + if (!this.dbPromise) { + this.dbPromise = new Promise((resolve, reject) => { + const request = indexedDB.open(this.dbName, 1) + request.onupgradeneeded = () => { + const db = request.result + let store: IDBObjectStore + if (!db.objectStoreNames.contains(this.storeName)) { + store = db.createObjectStore(this.storeName, { keyPath: ["kind", "id"] }) + } else { + store = request.transaction!.objectStore(this.storeName) + } + if (!store.indexNames.contains(this.kindIndex)) { + store.createIndex(this.kindIndex, "kind", { unique: false }) + } + } + request.onsuccess = () => resolve(request.result) + request.onerror = () => reject(request.error) + }) + } + return this.dbPromise + } + + private async read(fn: (store: IDBObjectStore) => IDBRequest): Promise { + const db = await this.open() + return new Promise((resolve, reject) => { + const transaction = db.transaction(this.storeName, "readonly") + const store = transaction.objectStore(this.storeName) + const request = fn(store) + request.onsuccess = () => resolve(request.result) + request.onerror = () => reject(request.error) + }) + } + + private async write(fn: (store: IDBObjectStore) => void): Promise { + const db = await this.open() + return new Promise((resolve, reject) => { + const transaction = db.transaction(this.storeName, "readwrite") + const store = transaction.objectStore(this.storeName) + fn(store) + transaction.oncomplete = () => resolve() + transaction.onerror = () => reject(transaction.error) + transaction.onabort = () => reject(transaction.error) + }) + } +} + +const supportsIndexedDb = () => typeof indexedDB !== "undefined" && typeof indexedDB.open === "function" + +const createCollectionStorage = ( + kind: K, +): CollectionStorage | null => { + if (supportsIndexedDb()) { + return new IndexedDbStorage(kind) + } + return null +} + +export { createCollectionStorage } +export type { CollectionStorage } diff --git a/web/packages/client/src/database/types.ts b/web/packages/client/src/database/types.ts new file mode 100644 index 000000000..e5b8ae842 --- /dev/null +++ b/web/packages/client/src/database/types.ts @@ -0,0 +1,29 @@ +import { DbModels, DbObjectKind } from "./models" + +/** Brand symbol to ensure refs are created only through db.ref() */ +declare const DbRefBrand: unique symbol + +/** This ref can be used to reference an object in the database. Must be obtained via db.ref(). */ +export type DbObjectRef = { + readonly kind: K + readonly id: number + readonly [DbRefBrand]: K +} + +export enum DbQueryPlanType { + Objects = 1, + Refs = 2, +} + +export type DbQueryPlan = { + key: string + type: DbQueryPlanType + kind: K + predicate: (object: O) => boolean +} + +export type DbQueryResult< + K extends DbObjectKind, + O extends DbModels[K], + P extends DbQueryPlan, +> = P extends DbQueryPlanType.Objects ? DbModels[P["kind"]][] : DbObjectRef[] diff --git a/web/packages/client/src/index.ts b/web/packages/client/src/index.ts new file mode 100644 index 000000000..b98ae9b9a --- /dev/null +++ b/web/packages/client/src/index.ts @@ -0,0 +1,7 @@ +export { Db, db } from "./database/index" +export * from "./database/models" +export * from "./database/react" +export * from "./database/types" +export * from "./auth" +export * from "./realtime" +export * from "./react" diff --git a/web/packages/client/src/react/index.tsx b/web/packages/client/src/react/index.tsx new file mode 100644 index 000000000..8d0104823 --- /dev/null +++ b/web/packages/client/src/react/index.tsx @@ -0,0 +1,197 @@ +import { + createContext, + type ReactNode, + useContext, + useEffect, + useMemo, + useRef, + useState, + useSyncExternalStore, +} from "react" +import { + useAuthActions as useAuthActionsBase, + useAuthState as useAuthStateBase, + useCurrentUserId as useCurrentUserIdBase, + useHasHydrated as useHasHydratedBase, + useIsLoggedIn as useIsLoggedInBase, + useToken as useTokenBase, +} from "@inline/auth" +import { auth as defaultAuth, type AuthState, type AuthStore } from "../auth" +import { db as defaultDb, type Db } from "../database" +import { RealtimeClient, type RealtimeConnectionState } from "../realtime" + +export type InlineClientContextValue = { + realtime: RealtimeClient + db: Db + auth: AuthStore +} + +const InlineClientContext = createContext(null) + +let defaultClient: InlineClientContextValue | null = null + +const getDefaultClient = () => { + if (!defaultClient) { + const auth = defaultAuth + const db = defaultDb + const realtime = new RealtimeClient({ auth, db }) + defaultClient = { auth, db, realtime } + } + return defaultClient +} + +export type InlineClientProviderProps = { + children: ReactNode + value: InlineClientContextValue +} + +export type InlineClientProviderOptions = { + value?: InlineClientContextValue + realtime?: RealtimeClient + db?: Db + auth?: AuthStore + autoConnect?: boolean +} + +export type InlineClientProviderState = { + value: InlineClientContextValue + hasDbHydrated: boolean +} + +export function useInlineClientProvider({ + value, + realtime, + db, + auth, + autoConnect = true, +}: InlineClientProviderOptions = {}): InlineClientProviderState { + const resolved = useMemo(() => { + if (value) return value + if (!auth && !db && !realtime) { + return getDefaultClient() + } + const resolvedAuth = auth ?? defaultAuth + const resolvedDb = db ?? defaultDb + const resolvedRealtime = realtime ?? new RealtimeClient({ auth: resolvedAuth, db: resolvedDb }) + return { auth: resolvedAuth, db: resolvedDb, realtime: resolvedRealtime } + }, [value, auth, db, realtime]) + + const reconnectRef = useRef(resolved) + reconnectRef.current = resolved + + useEffect(() => { + if (!autoConnect) return + + const syncConnection = () => { + const next = reconnectRef.current + if (next.auth.isLoggedIn()) { + void next.realtime.start() + } else { + void next.realtime.stop() + } + } + + syncConnection() + const unsubscribe = resolved.auth.subscribe(syncConnection) + return unsubscribe + }, [resolved, autoConnect]) + + const hasDbHydrated = useDbHasHydrated(resolved.db) + + return { value: resolved, hasDbHydrated } +} + +export function InlineClientProvider({ children, value }: InlineClientProviderProps) { + return {children} +} + +export function useInlineClient(): InlineClientContextValue { + return useContext(InlineClientContext) ?? getDefaultClient() +} + +export function useRealtimeClient(): RealtimeClient { + return useInlineClient().realtime +} + +export function useClientDb(): Db { + return useInlineClient().db +} + +export function useAuthStore(): AuthStore { + return useInlineClient().auth +} + +export function useDbHasHydrated(db?: Db): boolean { + const resolvedDb = db ?? useClientDb() + const [hydrated, setHydrated] = useState(resolvedDb.hasHydrated) + + useEffect(() => { + let active = true + if (resolvedDb.hasHydrated) { + if (resolvedDb.hydrationState === "pending") { + console.error("db hasHydrated true while hydration pending", resolvedDb.hydrationState) + } + if (resolvedDb.hydrationState === "failed") { + console.error("db hydration failed", resolvedDb.hydrationState) + } + console.log("db already hydrated", resolvedDb.hasHydrated) + setHydrated(true) + return () => { + active = false + } + } + + void resolvedDb.ready.then(() => { + if (resolvedDb.hydrationState === "failed") { + console.error("db hydration failed", resolvedDb.hydrationState) + } + console.log("db hydrated", resolvedDb.hasHydrated) + if (active) setHydrated(true) + }) + + return () => { + active = false + } + }, [resolvedDb]) + + return hydrated +} + +export function useAuthState(): AuthState { + const auth = useAuthStore() + return useAuthStateBase(auth) +} + +export function useIsLoggedIn(): boolean { + const auth = useAuthStore() + return useIsLoggedInBase(auth) +} + +export function useToken(): string | null { + const auth = useAuthStore() + return useTokenBase(auth) +} + +export function useCurrentUserId(): number | null { + const auth = useAuthStore() + return useCurrentUserIdBase(auth) +} + +export function useHasHydrated(): boolean { + const auth = useAuthStore() + return useHasHydratedBase(auth) +} + +export function useAuthActions() { + const auth = useAuthStore() + return useAuthActionsBase(auth) +} + +export function useConnectionState(): RealtimeConnectionState { + const realtime = useRealtimeClient() + return useSyncExternalStore( + (listener) => realtime.onConnectionState(() => listener()), + () => realtime.connectionState, + () => realtime.connectionState, + ) +} diff --git a/web/packages/client/src/react/useQuery.tsx b/web/packages/client/src/react/useQuery.tsx new file mode 100644 index 000000000..11721aba2 --- /dev/null +++ b/web/packages/client/src/react/useQuery.tsx @@ -0,0 +1,45 @@ +import { RpcResult } from "@in/protocol/core" +import { useEffect, useRef } from "react" +import { useInlineClient } from "src/react" +import { Transaction } from "src/realtime" + +type UseQueryOptions = { + dependencies?: unknown[] +} + +function dependenciesAreEqual(a: unknown[] | null | undefined, b: unknown[] | null | undefined) { + if (a == null && b != null) return false + if (a != null && b == null) return false + if (a == null && b == null) return true + + return (a ?? []).every((value, index) => value === b?.[index]) +} + +export const useQuery = (query: Transaction, options: UseQueryOptions = {}) => { + const client = useInlineClient() + + const state = useRef<{ + initial: boolean + inFlight: boolean + dependencies: unknown[] | null + }>({ initial: true, inFlight: false, dependencies: null }) + + useEffect(() => { + // Check dependencies have changed + if (!state.current.initial && dependenciesAreEqual(state.current.dependencies, options.dependencies)) return + state.current.initial = false + state.current.dependencies = options.dependencies ?? [] + state.current.inFlight = true + + console.log("querying", query.method) + client.realtime + .query(query) + .catch((error) => { + console.log("failed to query", error) + }) + .finally(() => { + console.log("finished querying", query.method) + state.current.inFlight = false + }) + }, [client.realtime, query.method, ...(options.dependencies ?? [])]) +} diff --git a/web/packages/client/src/realtime/__tests__/realtime.test.ts b/web/packages/client/src/realtime/__tests__/realtime.test.ts new file mode 100644 index 000000000..2aefa3f6c --- /dev/null +++ b/web/packages/client/src/realtime/__tests__/realtime.test.ts @@ -0,0 +1,219 @@ +import { describe, expect, it } from "vitest" +import { ServerProtocolMessage } from "@in/protocol/core" +import { AuthStore, Db, DbObjectKind, RealtimeClient } from "../../index" +import { getChats, getMe, logOut } from "../transactions" +import { MockTransport } from "../transport/mock-transport" + +const waitFor = async (predicate: () => boolean, timeoutMs = 300) => { + const start = Date.now() + while (Date.now() - start < timeoutMs) { + if (predicate()) return + await new Promise((resolve) => setTimeout(resolve, 5)) + } + throw new Error("Timed out waiting for condition") +} + +const connectAndOpen = async (transport: MockTransport) => { + await transport.connect() + await transport.emitMessage( + ServerProtocolMessage.create({ + id: 1n, + body: { oneofKind: "connectionOpen", connectionOpen: {} }, + }), + ) +} + +describe("realtime connection flow", () => { + it("sends connection init with token on connect", async () => { + const transport = new MockTransport() + const auth = new AuthStore() + const client = new RealtimeClient({ + auth, + transport, + url: "ws://example.test", + }) + + await client.startSession({ token: "test-token", userId: 42 }) + await transport.connect() + + await waitFor(() => transport.sent.length > 0) + + const initMessage = transport.sent[0] + expect(initMessage.body.oneofKind).toBe("connectionInit") + if (initMessage.body.oneofKind === "connectionInit") { + expect(initMessage.body.connectionInit.token).toBe("test-token") + } + + await client.stop() + }) + + it("moves to connected after connectionOpen", async () => { + const transport = new MockTransport() + const auth = new AuthStore() + const client = new RealtimeClient({ + auth, + transport, + url: "ws://example.test", + }) + + await client.startSession({ token: "test-token", userId: 1 }) + await connectAndOpen(transport) + + await waitFor(() => client.connectionState === "connected") + + expect(client.connectionState).toBe("connected") + + await client.stop() + }) + + it("executes getMe transaction and updates db", async () => { + const transport = new MockTransport() + const auth = new AuthStore() + const db = new Db() + const client = new RealtimeClient({ + auth, + db, + transport, + url: "ws://example.test", + }) + + await client.startSession({ token: "test-token", userId: 1 }) + await connectAndOpen(transport) + + const resultPromise = client.execute(getMe()) + + await waitFor(() => transport.sent.some((message) => message.body.oneofKind === "rpcCall")) + const rpcCallMessage = transport.sent.find((message) => message.body.oneofKind === "rpcCall") + if (!rpcCallMessage || rpcCallMessage.body.oneofKind !== "rpcCall") { + throw new Error("Missing rpcCall message") + } + + const rpcResult = ServerProtocolMessage.create({ + id: 2n, + body: { + oneofKind: "rpcResult", + rpcResult: { + reqMsgId: rpcCallMessage.id, + result: { + oneofKind: "getMe", + getMe: { + user: { + id: 99n, + firstName: "Ada", + }, + }, + }, + }, + }, + }) + + await transport.emitMessage(rpcResult) + await resultPromise + + const user = db.get(db.ref(DbObjectKind.User, 99)) + expect(user?.firstName).toBe("Ada") + + await client.stop() + }) + + it("executes getChats transaction and updates db", async () => { + const transport = new MockTransport() + const auth = new AuthStore() + const db = new Db() + const client = new RealtimeClient({ + auth, + db, + transport, + url: "ws://example.test", + }) + + await client.startSession({ token: "test-token", userId: 1 }) + await connectAndOpen(transport) + + const resultPromise = client.execute(getChats()) + + await waitFor(() => transport.sent.some((message) => message.body.oneofKind === "rpcCall")) + const rpcCallMessage = transport.sent.find((message) => message.body.oneofKind === "rpcCall") + if (!rpcCallMessage || rpcCallMessage.body.oneofKind !== "rpcCall") { + throw new Error("Missing rpcCall message") + } + + const rpcResult = ServerProtocolMessage.create({ + id: 3n, + body: { + oneofKind: "rpcResult", + rpcResult: { + reqMsgId: rpcCallMessage.id, + result: { + oneofKind: "getChats", + getChats: { + dialogs: [ + { + chatId: 10n, + peer: { type: { oneofKind: "chat", chat: { chatId: 10n } } }, + }, + ], + chats: [ + { + id: 10n, + title: "Test chat", + }, + ], + spaces: [], + users: [ + { + id: 200n, + firstName: "Taylor", + }, + ], + messages: [ + { + id: 300n, + fromId: 200n, + chatId: 10n, + out: false, + date: 1000n, + }, + ], + }, + }, + }, + }, + }) + + await transport.emitMessage(rpcResult) + await resultPromise + + const chat = db.get(db.ref(DbObjectKind.Chat, 10)) + const dialog = db.get(db.ref(DbObjectKind.Dialog, 10)) + const user = db.get(db.ref(DbObjectKind.User, 200)) + const message = db.get(db.ref(DbObjectKind.Message, 300)) + + expect(chat?.title).toBe("Test chat") + expect(dialog?.chatId).toBe(10) + expect(user?.firstName).toBe("Taylor") + expect(message?.chatId).toBe(10) + + await client.stop() + }) + + it("runs logOut transaction locally", async () => { + const transport = new MockTransport() + const auth = new AuthStore() + const client = new RealtimeClient({ + auth, + transport, + url: "ws://example.test", + }) + + await client.startSession({ token: "test-token", userId: 1 }) + await connectAndOpen(transport) + + await client.execute(logOut()) + + expect(auth.isLoggedIn()).toBe(false) + expect(client.connectionState).toBe("idle") + + await client.stop() + }) +}) diff --git a/web/packages/client/src/realtime/__tests__/transactions.test.ts b/web/packages/client/src/realtime/__tests__/transactions.test.ts new file mode 100644 index 000000000..e5b66f2d3 --- /dev/null +++ b/web/packages/client/src/realtime/__tests__/transactions.test.ts @@ -0,0 +1,227 @@ +import { describe, expect, it } from "vitest" +import { + ServerProtocolMessage, + Update, + type InputPeer, + type Message, + type UpdateDeleteMessages, + type UpdateEditMessage, + type UpdateMessageId, +} from "@in/protocol/core" +import { AuthStore, Db, DbObjectKind, RealtimeClient } from "../../index" +import { + deleteMessages, + editMessage, + getChatHistory, + sendMessage, +} from "../transactions" +import { MockTransport } from "../transport/mock-transport" +import { DbQueryPlanType } from "../../database/types" + +const waitFor = async (predicate: () => boolean, timeoutMs = 300) => { + const start = Date.now() + while (Date.now() - start < timeoutMs) { + if (predicate()) return + await new Promise((resolve) => setTimeout(resolve, 5)) + } + throw new Error("Timed out waiting for condition") +} + +const connectAndOpen = async (transport: MockTransport) => { + await transport.connect() + await transport.emitMessage( + ServerProtocolMessage.create({ + id: 1n, + body: { oneofKind: "connectionOpen", connectionOpen: {} }, + }), + ) +} + +const inputPeer: InputPeer = { type: { oneofKind: "chat", chat: { chatId: 10n } } } + +const buildRpcResult = (reqMsgId: bigint, result: { oneofKind: string } & Record) => + ServerProtocolMessage.create({ + id: 99n, + body: { + oneofKind: "rpcResult", + rpcResult: { + reqMsgId, + result: result as any, + }, + }, + }) + +describe("realtime transactions", () => { + it("updates temporary message id from sendMessage updates", async () => { + const transport = new MockTransport() + const auth = new AuthStore() + const db = new Db() + const client = new RealtimeClient({ auth, db, transport, url: "ws://example.test" }) + + await client.startSession({ token: "test-token", userId: 7 }) + await connectAndOpen(transport) + + const resultPromise = client.execute( + sendMessage({ + chatId: 10, + peerId: inputPeer, + text: "Hello", + }), + ) + + await waitFor(() => transport.sent.some((message) => message.body.oneofKind === "rpcCall")) + + const messages = db.queryCollection(DbQueryPlanType.Objects, DbObjectKind.Message, () => true) + expect(messages).toHaveLength(1) + const tempMessage = messages[0] + expect(tempMessage.message).toBe("Hello") + expect(tempMessage.randomId).toBeDefined() + + const rpcCallMessage = transport.sent.find((message) => message.body.oneofKind === "rpcCall") + if (!rpcCallMessage) throw new Error("Missing rpcCall message") + + const updateMessageId: UpdateMessageId = { + messageId: 500n, + randomId: tempMessage.randomId!, + } + + const update = Update.create({ update: { oneofKind: "updateMessageId", updateMessageId } }) + + await transport.emitMessage( + buildRpcResult(rpcCallMessage.id, { + oneofKind: "sendMessage", + sendMessage: { updates: [update] }, + }), + ) + + await resultPromise + + const updatedMessage = db.get(db.ref(DbObjectKind.Message, 500)) + expect(updatedMessage?.message).toBe("Hello") + expect(updatedMessage?.randomId).toBeUndefined() + + await client.stop() + }) + + it("applies editMessage updates", async () => { + const transport = new MockTransport() + const auth = new AuthStore() + const db = new Db() + const client = new RealtimeClient({ auth, db, transport, url: "ws://example.test" }) + + db.insert({ kind: DbObjectKind.Message, id: 101, fromId: 1, chatId: 10, message: "Old", out: false }) + + await client.startSession({ token: "test-token", userId: 1 }) + await connectAndOpen(transport) + + const resultPromise = client.execute(editMessage({ messageId: 101, peerId: inputPeer, text: "New" })) + + await waitFor(() => transport.sent.some((message) => message.body.oneofKind === "rpcCall")) + const rpcCallMessage = transport.sent.find((message) => message.body.oneofKind === "rpcCall") + if (!rpcCallMessage) throw new Error("Missing rpcCall message") + + const updated: Message = { + id: 101n, + fromId: 1n, + chatId: 10n, + out: false, + message: "New", + date: 1n, + } + + const updateEditMessage: UpdateEditMessage = { message: updated } + const update = Update.create({ update: { oneofKind: "editMessage", editMessage: updateEditMessage } }) + + await transport.emitMessage( + buildRpcResult(rpcCallMessage.id, { + oneofKind: "editMessage", + editMessage: { updates: [update] }, + }), + ) + + await resultPromise + + const message = db.get(db.ref(DbObjectKind.Message, 101)) + expect(message?.message).toBe("New") + + await client.stop() + }) + + it("applies deleteMessages updates", async () => { + const transport = new MockTransport() + const auth = new AuthStore() + const db = new Db() + const client = new RealtimeClient({ auth, db, transport, url: "ws://example.test" }) + + db.insert({ kind: DbObjectKind.Message, id: 201, fromId: 1, chatId: 10, message: "Delete", out: false }) + + await client.startSession({ token: "test-token", userId: 1 }) + await connectAndOpen(transport) + + const resultPromise = client.execute(deleteMessages({ messageIds: [201], peerId: inputPeer })) + + await waitFor(() => transport.sent.some((message) => message.body.oneofKind === "rpcCall")) + const rpcCallMessage = transport.sent.find((message) => message.body.oneofKind === "rpcCall") + if (!rpcCallMessage) throw new Error("Missing rpcCall message") + + const updateDeleteMessages: UpdateDeleteMessages = { + messageIds: [201n], + peerId: { type: { oneofKind: "chat", chat: { chatId: 10n } } }, + } + + const update = Update.create({ update: { oneofKind: "deleteMessages", deleteMessages: updateDeleteMessages } }) + + await transport.emitMessage( + buildRpcResult(rpcCallMessage.id, { + oneofKind: "deleteMessages", + deleteMessages: { updates: [update] }, + }), + ) + + await resultPromise + + const message = db.get(db.ref(DbObjectKind.Message, 201)) + expect(message).toBeUndefined() + + await client.stop() + }) + + it("upserts chat history messages", async () => { + const transport = new MockTransport() + const auth = new AuthStore() + const db = new Db() + const client = new RealtimeClient({ auth, db, transport, url: "ws://example.test" }) + + await client.startSession({ token: "test-token", userId: 1 }) + await connectAndOpen(transport) + + const resultPromise = client.execute(getChatHistory({ peerId: inputPeer, limit: 1 })) + + await waitFor(() => transport.sent.some((message) => message.body.oneofKind === "rpcCall")) + const rpcCallMessage = transport.sent.find((message) => message.body.oneofKind === "rpcCall") + if (!rpcCallMessage) throw new Error("Missing rpcCall message") + + const message: Message = { + id: 301n, + fromId: 2n, + chatId: 10n, + out: false, + message: "History", + date: 1n, + } + + await transport.emitMessage( + buildRpcResult(rpcCallMessage.id, { + oneofKind: "getChatHistory", + getChatHistory: { messages: [message] }, + }), + ) + + await resultPromise + + const stored = db.get(db.ref(DbObjectKind.Message, 301)) + expect(stored?.message).toBe("History") + + await client.stop() + }) +}) diff --git a/web/packages/client/src/realtime/client/ping-pong.ts b/web/packages/client/src/realtime/client/ping-pong.ts new file mode 100644 index 000000000..0a8fe0d67 --- /dev/null +++ b/web/packages/client/src/realtime/client/ping-pong.ts @@ -0,0 +1,144 @@ +import { Log } from "@inline/log" +import type { ProtocolClient } from "./protocol-client" + +export class PingPongService { + private readonly log: Log + private client: ProtocolClient | null = null + private running = false + private sleepTimer: ReturnType | null = null + private sleepResolver: (() => void) | null = null + + private pings = new Map() + private recentLatenciesMs: number[] = [] + + constructor(logger?: Log) { + this.log = logger ?? new Log("RealtimeV2.PingPongService", "info") + } + + configure(client: ProtocolClient) { + this.client = client + } + + start() { + if (this.running) return + this.log.debug("starting ping pong service") + this.running = true + this.reset() + void this.loop() + } + + stop() { + this.log.debug("stopping ping pong service") + this.running = false + this.clearSleep() + this.reset() + } + + async ping() { + const client = this.client + if (!client) return + if (client.state !== "open") return + + const nonce = this.randomNonce() + this.log.debug("ping sent with nonce", nonce) + await client.sendPing(nonce) + this.pings.set(nonce, Date.now()) + } + + pong(nonce: bigint) { + this.log.debug("pong received for nonce", nonce) + const pingDate = this.pings.get(nonce) + if (!pingDate) { + this.log.trace("pong received for unknown ping nonce", nonce) + return + } + + this.pings.delete(nonce) + this.recordLatency(pingDate) + this.log.debug("avg latency", this.avgLatencyMs(), "ms") + } + + private async loop() { + while (this.running) { + await this.checkConnection() + + const delay = this.getNextPingDelayMs() + await this.sleep(delay) + if (!this.running) break + + await this.ping() + } + } + + private reset() { + this.pings.clear() + this.recentLatenciesMs = [] + } + + private getNextPingDelayMs() { + if (this.avgLatencyMs() > 2000) { + this.log.debug("avg latency is high, increasing ping interval") + return 25_000 + } + return 10_000 + } + + private async checkConnection() { + const client = this.client + if (!client) return + if (client.state !== "open") return + + const now = Date.now() + const hasTimedOutPing = [...this.pings.values()].some((timestamp) => now - timestamp > 30_000) + if (!hasTimedOutPing) return + + await client.reconnect() + } + + private recordLatency(pingDateMs: number) { + const latency = Date.now() - pingDateMs + this.recentLatenciesMs.push(latency) + if (this.recentLatenciesMs.length > 10) { + this.recentLatenciesMs.shift() + } + } + + private avgLatencyMs() { + if (this.recentLatenciesMs.length === 0) return 0 + const sum = this.recentLatenciesMs.reduce((acc, value) => acc + value, 0) + return Math.round(sum / this.recentLatenciesMs.length) + } + + private async sleep(ms: number) { + if (ms <= 0) return + await new Promise((resolve) => { + this.sleepResolver = resolve + this.sleepTimer = setTimeout(() => { + this.sleepTimer = null + this.sleepResolver = null + resolve() + }, ms) + }) + } + + private clearSleep() { + if (this.sleepTimer) { + clearTimeout(this.sleepTimer) + this.sleepTimer = null + } + if (this.sleepResolver) { + this.sleepResolver() + this.sleepResolver = null + } + } + + private randomNonce(): bigint { + if (typeof crypto !== "undefined" && "getRandomValues" in crypto) { + const buffer = new Uint32Array(2) + crypto.getRandomValues(buffer) + return (BigInt(buffer[0]) << 32n) | BigInt(buffer[1]) + } + + return BigInt(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)) + } +} diff --git a/web/packages/client/src/realtime/client/protocol-client.ts b/web/packages/client/src/realtime/client/protocol-client.ts new file mode 100644 index 000000000..aa9e4a5f2 --- /dev/null +++ b/web/packages/client/src/realtime/client/protocol-client.ts @@ -0,0 +1,399 @@ +import { ClientMessage, ConnectionInit, ServerProtocolMessage } from "@in/protocol/core" +import type { Method, RpcCall, RpcError, RpcResult } from "@in/protocol/core" +import { Log, type LogLevel } from "@inline/log" +import { AsyncChannel } from "../../utils/async-channel" +import { PingPongService } from "./ping-pong" +import type { Transport } from "../transport/transport" +import type { ClientEvent, ClientState } from "../types" + +export type ProtocolClientOptions = { + transport: Transport + getConnectionInit: () => ConnectionInit | null + logLevel?: LogLevel + logger?: Log +} + +type RpcContinuation = { + resolve: (value: RpcResult["result"]) => void + reject: (error: Error) => void + timeout?: ReturnType +} + +const emptyRpcInput: RpcCall["input"] = { oneofKind: undefined } + +export class ProtocolClient { + readonly events = new AsyncChannel() + readonly transport: Transport + readonly pingPong: PingPongService + + state: ClientState = "connecting" + + private readonly log: Log + private readonly getConnectionInit: () => ConnectionInit | null + + private rpcContinuations = new Map() + + private seq = 0 + private lastTimestamp = 0 + private sequence = 0 + private readonly epochSeconds = 1_735_689_600 + + private connectionAttemptNo = 0 + private reconnectionTimer: ReturnType | null = null + private authenticationTimeout: ReturnType | null = null + private listenersStarted = false + + constructor(options: ProtocolClientOptions) { + const baseLogger = options.logger ?? new Log("RealtimeV2.ProtocolClient", options.logLevel) + + this.transport = options.transport + this.log = baseLogger + this.getConnectionInit = options.getConnectionInit + + this.pingPong = new PingPongService(baseLogger.withScope("PingPongService")) + this.pingPong.configure(this) + + this.startListeners() + } + + async startTransport() { + await this.transport.start() + } + + async stopTransport() { + await this.transport.stop() + } + + async sendPing(nonce: bigint) { + const message = this.wrapMessage({ + oneofKind: "ping", + ping: { + nonce, + }, + }) + + try { + await this.transport.send(message) + } catch (error) { + this.log.error("Failed to send ping", error) + } + } + + async reconnect(options?: { skipDelay?: boolean }) { + this.log.trace("Reconnecting transport") + await this.transport.reconnect({ skipDelay: options?.skipDelay }) + } + + async sendRpc(method: Method, input: RpcCall["input"] = emptyRpcInput): Promise { + const message = this.wrapMessage({ + oneofKind: "rpcCall", + rpcCall: { + method, + input, + }, + }) + + await this.transport.send(message) + return message.id + } + + async callRpc( + method: Method, + input: RpcCall["input"] = emptyRpcInput, + options?: { timeoutMs?: number }, + ): Promise { + const message = this.wrapMessage({ + oneofKind: "rpcCall", + rpcCall: { + method, + input, + }, + }) + + return await new Promise((resolve, reject) => { + const continuation: RpcContinuation = { resolve, reject } + this.rpcContinuations.set(message.id, continuation) + + void this.transport.send(message).catch((error) => { + this.failRpcContinuation(message.id, error) + }) + + const timeoutMs = options?.timeoutMs ?? 15_000 + if (timeoutMs > 0) { + continuation.timeout = setTimeout(() => { + if (!this.rpcContinuations.has(message.id)) return + this.failRpcContinuation(message.id, new ProtocolClientError("timeout")) + }, timeoutMs) + } + }) + } + + private async startListeners() { + if (this.listenersStarted) return + this.listenersStarted = true + + ;(async () => { + for await (const event of this.transport.events) { + switch (event.type) { + case "connected": + this.log.trace("Protocol client: Transport connected") + await this.authenticate() + break + + case "message": + this.log.trace("Protocol client received transport message", event.message) + await this.handleTransportMessage(event.message) + break + + case "connecting": + await this.connecting() + break + + case "stopping": + this.log.trace("Protocol client: Transport stopping. Resetting state") + await this.reset() + break + } + } + })().catch((error) => { + this.log.error("Protocol client listener crashed", error) + }) + } + + private async handleTransportMessage(message: ServerProtocolMessage) { + switch (message.body.oneofKind) { + case "connectionOpen": + await this.connectionOpen() + this.log.info("Protocol client: Connection established") + break + + case "rpcResult": + this.completeRpcResult(message.body.rpcResult.reqMsgId, message.body.rpcResult.result) + await this.events.send({ + type: "rpcResult", + msgId: message.body.rpcResult.reqMsgId, + rpcResult: message.body.rpcResult.result, + }) + break + + case "rpcError": + this.completeRpcError(message.body.rpcError.reqMsgId, message.body.rpcError) + await this.events.send({ + type: "rpcError", + msgId: message.body.rpcError.reqMsgId, + rpcError: message.body.rpcError, + }) + break + + case "ack": + this.log.trace("Received ack", message.body.ack.msgId) + await this.events.send({ type: "ack", msgId: message.body.ack.msgId }) + break + + case "message": + this.log.trace("Received server message", message.body.message) + if (message.body.message.payload.oneofKind === "update") { + await this.events.send({ type: "updates", updates: message.body.message.payload.update }) + } else { + this.log.trace("Protocol client: Unhandled message type") + } + break + + case "pong": + this.log.trace("Received pong", message.body.pong.nonce) + this.pingPong.pong(message.body.pong.nonce) + break + + case "connectionError": + this.log.error("Protocol client: connection error") + this.handleClientFailure() + break + + default: + this.log.trace("Protocol client: Unhandled message type", message.body) + break + } + } + + private async sendConnectionInit() { + this.log.trace("sending connection init") + + const connectionInit = this.getConnectionInit() + if (!connectionInit) { + this.log.error("No token available for connection init") + throw new ProtocolClientError("not-authorized") + } + + const message = this.wrapMessage({ + oneofKind: "connectionInit", + connectionInit, + }) + + await this.transport.send(message) + this.log.trace("connection init sent successfully") + } + + private async authenticate() { + try { + await this.sendConnectionInit() + this.log.trace("Sent authentication message") + this.startAuthenticationTimeout() + } catch (error) { + this.log.error("Failed to authenticate, attempting restart", error) + this.handleClientFailure() + } + } + + private async connectionOpen() { + this.state = "open" + await this.events.send({ type: "open" }) + this.stopAuthenticationTimeout() + if (this.reconnectionTimer) { + clearTimeout(this.reconnectionTimer) + this.reconnectionTimer = null + } + this.connectionAttemptNo = 0 + this.pingPong.start() + } + + private async connecting() { + this.state = "connecting" + await this.events.send({ type: "connecting" }) + this.stopAuthenticationTimeout() + this.pingPong.stop() + this.cancelAllRpcContinuations(new ProtocolClientError("not-connected")) + } + + private async reset() { + this.seq = 0 + this.lastTimestamp = 0 + this.sequence = 0 + this.connectionAttemptNo = 0 + this.stopAuthenticationTimeout() + if (this.reconnectionTimer) { + clearTimeout(this.reconnectionTimer) + this.reconnectionTimer = null + } + this.pingPong.stop() + this.cancelAllRpcContinuations(new ProtocolClientError("stopped")) + } + + private startAuthenticationTimeout() { + this.stopAuthenticationTimeout() + this.authenticationTimeout = setTimeout(() => { + if (this.state === "connecting") { + this.log.error("Authentication timeout. Reconnecting") + void this.reconnect({ skipDelay: true }) + } + }, 10_000) + } + + private stopAuthenticationTimeout() { + if (!this.authenticationTimeout) return + clearTimeout(this.authenticationTimeout) + this.authenticationTimeout = null + } + + private handleClientFailure() { + this.log.debug("Client failure. Reconnecting") + this.connectionAttemptNo = (this.connectionAttemptNo + 1) >>> 0 + this.stopAuthenticationTimeout() + + if (this.reconnectionTimer) { + clearTimeout(this.reconnectionTimer) + } + + this.reconnectionTimer = setTimeout(() => { + if (this.state === "open") return + void this.reconnect({ skipDelay: true }) + }, this.getReconnectionDelay() * 1000) + } + + private getReconnectionDelay() { + const attemptNo = this.connectionAttemptNo + + if (attemptNo >= 8) { + return 8.0 + Math.random() * 5.0 + } + + return Math.min(8.0, 0.2 + Math.pow(attemptNo, 1.5) * 0.4) + } + + private wrapMessage(body: ClientMessage["body"]): ClientMessage { + this.advanceSeq() + return ClientMessage.create({ + id: this.generateId(), + seq: this.seq, + body, + }) + } + + private advanceSeq() { + this.seq = (this.seq + 1) >>> 0 + } + + private generateId(): bigint { + const timestamp = this.currentTimestamp() + if (timestamp === this.lastTimestamp) { + this.sequence = (this.sequence + 1) >>> 0 + } else { + this.sequence = 0 + this.lastTimestamp = timestamp + } + + return (BigInt(timestamp) << 32n) | BigInt(this.sequence) + } + + private currentTimestamp() { + return Math.floor(Date.now() / 1000) - this.epochSeconds + } + + private completeRpcResult(msgId: bigint, rpcResult: RpcResult["result"]) { + const continuation = this.getAndRemoveRpcContinuation(msgId) + continuation?.resolve(rpcResult) + } + + private completeRpcError(msgId: bigint, rpcError: RpcError) { + const error = new ProtocolClientError("rpc-error", { + code: rpcError.code, + message: rpcError.message, + }) + const continuation = this.getAndRemoveRpcContinuation(msgId) + continuation?.reject(error) + } + + private failRpcContinuation(msgId: bigint, error: Error) { + const continuation = this.getAndRemoveRpcContinuation(msgId) + continuation?.reject(error) + } + + private getAndRemoveRpcContinuation(msgId: bigint) { + const continuation = this.rpcContinuations.get(msgId) + if (!continuation) return null + if (continuation.timeout) { + clearTimeout(continuation.timeout) + } + this.rpcContinuations.delete(msgId) + return continuation + } + + private cancelAllRpcContinuations(error: Error) { + for (const continuation of this.rpcContinuations.values()) { + continuation.reject(error) + if (continuation.timeout) { + clearTimeout(continuation.timeout) + } + } + this.rpcContinuations.clear() + } +} + +class ProtocolClientError extends Error { + constructor( + code: "not-authorized" | "not-connected" | "rpc-error" | "stopped" | "timeout", + details?: { code?: number; message?: string }, + ) { + super(details?.message ?? code) + this.name = `ProtocolClientError:${code}` + } +} diff --git a/web/packages/client/src/realtime/index.ts b/web/packages/client/src/realtime/index.ts new file mode 100644 index 000000000..4769eaf33 --- /dev/null +++ b/web/packages/client/src/realtime/index.ts @@ -0,0 +1,4 @@ +export type { RealtimeConnectionState } from "./types" +export { RealtimeClient } from "./realtime" +export type { RealtimeClientOptions } from "./realtime" +export * from "./transactions" diff --git a/web/packages/client/src/realtime/realtime.ts b/web/packages/client/src/realtime/realtime.ts new file mode 100644 index 000000000..a7453f6a8 --- /dev/null +++ b/web/packages/client/src/realtime/realtime.ts @@ -0,0 +1,298 @@ +import type { ConnectionInit, RpcError, RpcResult } from "@in/protocol/core" +import { Log, type LogLevel } from "@inline/log" +import { getRealtimeUrl } from "@inline/config" +import { AsyncChannel } from "../utils/async-channel" +import { Emitter } from "../utils/emitter" +import { auth as defaultAuth, type AuthSession, type AuthStore } from "../auth" +import { db as defaultDb, type Db } from "../database" +import { ProtocolClient } from "./client/protocol-client" +import type { ClientEvent, RealtimeConnectionState } from "./types" +import { WebSocketTransport } from "./transport/ws-transport" +import type { Transport } from "./transport/transport" +import type { LocalTransaction, Transaction } from "./transactions" +import { Transactions, TransactionErrors, TransactionFailure } from "./transactions" +import { applyUpdates } from "./updates" + +export type RealtimeClientOptions = { + url?: string + logLevel?: LogLevel + logger?: Log + transport?: Transport + auth?: AuthStore + db?: Db + buildNumber?: number + layer?: number +} + +type TransactionContinuation = { + resolve: (value: RpcResult["result"] | undefined) => void + reject: (error: Error) => void +} + +const isLocalTransaction = (transaction: Transaction): transaction is LocalTransaction => + (transaction as LocalTransaction).localOnly === true + +export class RealtimeClient { + readonly transport: Transport + readonly client: ProtocolClient + readonly auth: AuthStore + readonly db: Db + + connectionState: RealtimeConnectionState = "idle" + + private readonly connectionInfo: { buildNumber?: number; layer?: number } + private readonly log: Log + private readonly stateChannel = new AsyncChannel() + private readonly stateEmitter = new Emitter() + private readonly transactions: Transactions + private readonly transactionContinuations = new Map() + private listenersStarted = false + private started = false + + constructor(options: RealtimeClientOptions = {}) { + const baseLogger = options.logger ?? new Log("RealtimeV2", options.logLevel) + this.log = baseLogger + + this.auth = options.auth ?? defaultAuth + this.db = options.db ?? defaultDb + + const resolvedLayer = options.layer ?? 2 + this.connectionInfo = { + ...(options.buildNumber != null ? { buildNumber: options.buildNumber } : {}), + layer: resolvedLayer, + } + + const url = options.url ?? getRealtimeUrl() + + this.transport = + options.transport ?? + new WebSocketTransport({ + url, + logLevel: options.logLevel, + logger: baseLogger.withScope("WebSocketTransport"), + }) + + this.client = new ProtocolClient({ + transport: this.transport, + getConnectionInit: () => this.getConnectionInit(), + logLevel: options.logLevel, + logger: baseLogger.withScope("ProtocolClient"), + }) + + this.transactions = new Transactions(baseLogger.withScope("Transactions")) + + this.startListeners() + } + + async start() { + if (this.started) return + if (!this.getConnectionInit()) { + throw new Error("not-authorized") + } + + this.started = true + this.updateConnectionState("connecting") + await this.client.startTransport() + } + + async stop() { + if (!this.started) return + this.started = false + await this.client.stopTransport() + this.updateConnectionState("idle") + + const pending = this.transactions.reset() + for (const wrapper of pending) { + await wrapper.transaction.cancelled?.(this.db, this.auth) + const continuation = this.transactionContinuations.get(wrapper.id) + if (continuation) { + continuation.reject(new TransactionFailure(TransactionErrors.stopped())) + this.transactionContinuations.delete(wrapper.id) + } + } + } + + async startSession(session: AuthSession) { + this.auth.login(session) + await this.start() + } + + async stopSession() { + await this.stop() + this.auth.logout() + } + + private getConnectionInit(): ConnectionInit | null { + const token = this.auth.getToken() + if (!token) return null + + return { + token, + ...(this.connectionInfo.buildNumber != null ? { buildNumber: this.connectionInfo.buildNumber } : {}), + ...(this.connectionInfo.layer != null ? { layer: this.connectionInfo.layer } : {}), + } + } + + connectionStates() { + return this.stateChannel + } + + onConnectionState(listener: (state: RealtimeConnectionState) => void) { + return this.stateEmitter.subscribe(listener) + } + + async execute(transaction: Transaction): Promise { + await transaction.optimistic?.(this.db, this.auth) + + if (isLocalTransaction(transaction)) { + await transaction.runLocal({ auth: this.auth, db: this.db, stopRealtime: () => this.stop() }) + return undefined + } + + const transactionId = this.transactions.enqueue(transaction) + this.log.trace("Queued transaction", transaction.describe?.() ?? transaction.method) + + return await new Promise((resolve, reject) => { + this.transactionContinuations.set(transactionId, { resolve, reject }) + if (this.connectionState === "connected") { + void this.flushQueue() + } + }) + } + + async query(transaction: Transaction): Promise { + return await this.execute(transaction) + } + + async mutate(transaction: Transaction): Promise { + return await this.execute(transaction) + } + + cancelTransactions(predicate: (transaction: Transaction) => boolean) { + const cancelled = this.transactions.cancel((wrapper) => predicate(wrapper.transaction)) + for (const wrapper of cancelled) { + void wrapper.transaction.cancelled?.(this.db, this.auth) + const continuation = this.transactionContinuations.get(wrapper.id) + if (continuation) { + continuation.reject(new TransactionFailure(TransactionErrors.stopped())) + this.transactionContinuations.delete(wrapper.id) + } + } + } + + private async startListeners() { + if (this.listenersStarted) return + this.listenersStarted = true + + ;(async () => { + for await (const event of this.client.events) { + await this.handleClientEvent(event) + } + })().catch((error) => { + this.log.error("Realtime listener crashed", error) + }) + + ;(async () => { + for await (const _ of this.transactions.queueStream) { + if (this.connectionState !== "connected") continue + await this.flushQueue() + } + })().catch((error) => { + this.log.error("Transaction loop crashed", error) + }) + } + + private async handleClientEvent(event: ClientEvent) { + switch (event.type) { + case "open": + this.log.trace("Protocol client open") + this.updateConnectionState("connected") + this.transactions.requeueAll() + await this.flushQueue() + break + + case "connecting": + this.updateConnectionState("connecting") + break + + case "ack": + this.transactions.ack(event.msgId) + break + + case "rpcResult": + await this.completeTransaction(event.msgId, event.rpcResult) + break + + case "rpcError": + await this.failTransaction(event.msgId, event.rpcError) + break + + case "updates": + this.log.trace("Updates received", event.updates) + applyUpdates(this.db, event.updates.updates) + break + + default: + break + } + } + + private async flushQueue() { + if (this.connectionState !== "connected") return + while (this.connectionState === "connected") { + const wrapper = this.transactions.dequeue() + if (!wrapper) return + await this.runTransaction(wrapper) + } + } + + private async runTransaction(wrapper: { id: string; transaction: Transaction }) { + const transaction = wrapper.transaction + try { + const msgId = await this.client.sendRpc(transaction.method, transaction.input(transaction.context)) + this.transactions.running(wrapper.id, msgId) + } catch (error) { + this.log.error("Failed to send transaction", error) + this.transactions.requeue(wrapper.id) + } + } + + private async completeTransaction(msgId: bigint, rpcResult: RpcResult["result"]) { + const wrapper = this.transactions.complete(msgId) + if (!wrapper) return + + const transaction = wrapper.transaction + const continuation = this.transactionContinuations.get(wrapper.id) + this.transactionContinuations.delete(wrapper.id) + + try { + await transaction.apply(rpcResult, this.db) + continuation?.resolve(rpcResult) + } catch (error) { + this.log.error("Failed to apply transaction", error) + const txError = TransactionErrors.invalid() + await transaction.failed?.(txError, this.db, this.auth) + continuation?.reject(new TransactionFailure(txError)) + } + } + + private async failTransaction(msgId: bigint, rpcError: RpcError) { + const wrapper = this.transactions.complete(msgId) + if (!wrapper) return + + const transaction = wrapper.transaction + const continuation = this.transactionContinuations.get(wrapper.id) + this.transactionContinuations.delete(wrapper.id) + + const error = TransactionErrors.rpcError(rpcError.code, rpcError.message) + await transaction.failed?.(error, this.db, this.auth) + continuation?.reject(new TransactionFailure(error)) + } + + private updateConnectionState(state: RealtimeConnectionState) { + if (this.connectionState === state) return + this.connectionState = state + void this.stateChannel.send(state) + this.stateEmitter.emit(state) + } +} diff --git a/web/packages/client/src/realtime/transactions/add-reaction.ts b/web/packages/client/src/realtime/transactions/add-reaction.ts new file mode 100644 index 000000000..275da24b5 --- /dev/null +++ b/web/packages/client/src/realtime/transactions/add-reaction.ts @@ -0,0 +1,42 @@ +import type { AddReactionInput, InputPeer, RpcCall, RpcResult } from "@in/protocol/core" +import { Method } from "@in/protocol/core" +import type { Db } from "../../database" +import { applyUpdates } from "../updates" +import { Mutation, type Transaction } from "./transaction" + +export type AddReactionContext = { + emoji: string + messageId: number + peerId?: InputPeer +} + +export class AddReactionTransaction implements Transaction { + readonly method = Method.ADD_REACTION + readonly kind = Mutation() + readonly context: AddReactionContext + + constructor(context: AddReactionContext) { + this.context = context + } + + input(context: AddReactionContext) { + const payload: AddReactionInput = { + emoji: context.emoji, + messageId: BigInt(context.messageId), + peerId: context.peerId, + } + + const input: RpcCall["input"] = { oneofKind: "addReaction", addReaction: payload } + return input + } + + async apply(result: RpcResult["result"] | undefined, db: Db) { + if (!result || result.oneofKind !== "addReaction") { + throw new Error("invalid") + } + + applyUpdates(db, result.addReaction.updates) + } +} + +export const addReaction = (context: AddReactionContext) => new AddReactionTransaction(context) diff --git a/web/packages/client/src/realtime/transactions/create-chat.ts b/web/packages/client/src/realtime/transactions/create-chat.ts new file mode 100644 index 000000000..4f6455675 --- /dev/null +++ b/web/packages/client/src/realtime/transactions/create-chat.ts @@ -0,0 +1,54 @@ +import type { CreateChatInput, InputChatParticipant, RpcCall, RpcResult } from "@in/protocol/core" +import { Method } from "@in/protocol/core" +import type { Db } from "../../database" +import { upsertChat, upsertDialog } from "./mappers" +import { Mutation, type Transaction } from "./transaction" +import { toBigInt } from "./helpers" + +export type CreateChatContext = { + title: string + spaceId?: number + description?: string + emoji?: string + isPublic: boolean + participants?: InputChatParticipant[] +} + +export class CreateChatTransaction implements Transaction { + readonly method = Method.CREATE_CHAT + readonly kind = Mutation() + readonly context: CreateChatContext + + constructor(context: CreateChatContext) { + this.context = context + } + + input(context: CreateChatContext) { + const payload: CreateChatInput = { + title: context.title, + spaceId: toBigInt(context.spaceId), + description: context.description, + emoji: context.emoji, + isPublic: context.isPublic, + participants: context.participants ?? [], + } + + const input: RpcCall["input"] = { oneofKind: "createChat", createChat: payload } + return input + } + + async apply(result: RpcResult["result"] | undefined, db: Db) { + if (!result || result.oneofKind !== "createChat") { + throw new Error("invalid") + } + + if (result.createChat.chat) { + upsertChat(db, result.createChat.chat) + } + if (result.createChat.dialog) { + upsertDialog(db, result.createChat.dialog) + } + } +} + +export const createChat = (context: CreateChatContext) => new CreateChatTransaction(context) diff --git a/web/packages/client/src/realtime/transactions/delete-messages.ts b/web/packages/client/src/realtime/transactions/delete-messages.ts new file mode 100644 index 000000000..fdf4fc813 --- /dev/null +++ b/web/packages/client/src/realtime/transactions/delete-messages.ts @@ -0,0 +1,48 @@ +import type { DeleteMessagesInput, InputPeer, RpcCall, RpcResult } from "@in/protocol/core" +import { Method } from "@in/protocol/core" +import type { Db } from "../../database" +import type { AuthStore } from "../../auth" +import { DbObjectKind } from "../../database/models" +import { applyUpdates } from "../updates" +import { Mutation, type Transaction } from "./transaction" + +export type DeleteMessagesContext = { + messageIds: number[] + peerId?: InputPeer +} + +export class DeleteMessagesTransaction implements Transaction { + readonly method = Method.DELETE_MESSAGES + readonly kind = Mutation() + readonly context: DeleteMessagesContext + + constructor(context: DeleteMessagesContext) { + this.context = context + } + + input(context: DeleteMessagesContext) { + const payload: DeleteMessagesInput = { + messageIds: context.messageIds.map((id) => BigInt(id)), + peerId: context.peerId, + } + + const input: RpcCall["input"] = { oneofKind: "deleteMessages", deleteMessages: payload } + return input + } + + async optimistic(db: Db, _auth: AuthStore) { + for (const id of this.context.messageIds) { + db.delete(db.ref(DbObjectKind.Message, id)) + } + } + + async apply(result: RpcResult["result"] | undefined, db: Db) { + if (!result || result.oneofKind !== "deleteMessages") { + throw new Error("invalid") + } + + applyUpdates(db, result.deleteMessages.updates) + } +} + +export const deleteMessages = (context: DeleteMessagesContext) => new DeleteMessagesTransaction(context) diff --git a/web/packages/client/src/realtime/transactions/delete-reaction.ts b/web/packages/client/src/realtime/transactions/delete-reaction.ts new file mode 100644 index 000000000..d7f3a94bc --- /dev/null +++ b/web/packages/client/src/realtime/transactions/delete-reaction.ts @@ -0,0 +1,42 @@ +import type { DeleteReactionInput, InputPeer, RpcCall, RpcResult } from "@in/protocol/core" +import { Method } from "@in/protocol/core" +import type { Db } from "../../database" +import { applyUpdates } from "../updates" +import { Mutation, type Transaction } from "./transaction" + +export type DeleteReactionContext = { + emoji: string + messageId: number + peerId?: InputPeer +} + +export class DeleteReactionTransaction implements Transaction { + readonly method = Method.DELETE_REACTION + readonly kind = Mutation() + readonly context: DeleteReactionContext + + constructor(context: DeleteReactionContext) { + this.context = context + } + + input(context: DeleteReactionContext) { + const payload: DeleteReactionInput = { + emoji: context.emoji, + messageId: BigInt(context.messageId), + peerId: context.peerId, + } + + const input: RpcCall["input"] = { oneofKind: "deleteReaction", deleteReaction: payload } + return input + } + + async apply(result: RpcResult["result"] | undefined, db: Db) { + if (!result || result.oneofKind !== "deleteReaction") { + throw new Error("invalid") + } + + applyUpdates(db, result.deleteReaction.updates) + } +} + +export const deleteReaction = (context: DeleteReactionContext) => new DeleteReactionTransaction(context) diff --git a/web/packages/client/src/realtime/transactions/edit-message.ts b/web/packages/client/src/realtime/transactions/edit-message.ts new file mode 100644 index 000000000..0c1f3aa7d --- /dev/null +++ b/web/packages/client/src/realtime/transactions/edit-message.ts @@ -0,0 +1,58 @@ +import type { EditMessageInput, InputPeer, MessageEntities, RpcCall, RpcResult } from "@in/protocol/core" +import { Method } from "@in/protocol/core" +import type { Db } from "../../database" +import type { AuthStore } from "../../auth" +import { DbObjectKind } from "../../database/models" +import { applyUpdates } from "../updates" +import { Mutation, type Transaction } from "./transaction" + +export type EditMessageContext = { + messageId: number + peerId?: InputPeer + text: string + entities?: MessageEntities +} + +export class EditMessageTransaction implements Transaction { + readonly method = Method.EDIT_MESSAGE + readonly kind = Mutation() + readonly context: EditMessageContext + + constructor(context: EditMessageContext) { + this.context = context + } + + input(context: EditMessageContext) { + const payload: EditMessageInput = { + messageId: BigInt(context.messageId), + peerId: context.peerId, + text: context.text, + entities: context.entities, + } + + const input: RpcCall["input"] = { oneofKind: "editMessage", editMessage: payload } + return input + } + + async optimistic(db: Db, _auth: AuthStore) { + const ref = db.ref(DbObjectKind.Message, this.context.messageId) + const existing = db.get(ref) + if (!existing) return + + db.update({ + ...existing, + message: this.context.text, + editDate: Math.floor(Date.now() / 1000), + }) + } + + async apply(result: RpcResult["result"] | undefined, db: Db) { + if (!result || result.oneofKind !== "editMessage") { + throw new Error("invalid") + } + + applyUpdates(db, result.editMessage.updates) + } +} + +export const editMessage = (context: EditMessageContext) => new EditMessageTransaction(context) diff --git a/web/packages/client/src/realtime/transactions/get-chat-history.ts b/web/packages/client/src/realtime/transactions/get-chat-history.ts new file mode 100644 index 000000000..342674094 --- /dev/null +++ b/web/packages/client/src/realtime/transactions/get-chat-history.ts @@ -0,0 +1,47 @@ +import type { GetChatHistoryInput, InputPeer, RpcCall, RpcResult } from "@in/protocol/core" +import { Method } from "@in/protocol/core" +import type { Db } from "../../database" +import { upsertMessage } from "./mappers" +import { Query, type Transaction } from "./transaction" +import { toBigInt } from "./helpers" + +export type GetChatHistoryContext = { + peerId?: InputPeer + offsetId?: number + limit?: number +} + +export class GetChatHistoryTransaction implements Transaction { + readonly method = Method.GET_CHAT_HISTORY + readonly kind = Query() + readonly context: GetChatHistoryContext + + constructor(context: GetChatHistoryContext) { + this.context = context + } + + input(context: GetChatHistoryContext) { + const payload: GetChatHistoryInput = { + peerId: context.peerId, + offsetId: toBigInt(context.offsetId), + limit: context.limit, + } + + const input: RpcCall["input"] = { oneofKind: "getChatHistory", getChatHistory: payload } + return input + } + + async apply(result: RpcResult["result"] | undefined, db: Db) { + if (!result || result.oneofKind !== "getChatHistory") { + throw new Error("invalid") + } + + db.batch(() => { + for (const message of result.getChatHistory.messages) { + upsertMessage(db, message) + } + }) + } +} + +export const getChatHistory = (context: GetChatHistoryContext) => new GetChatHistoryTransaction(context) diff --git a/web/packages/client/src/realtime/transactions/get-chat.ts b/web/packages/client/src/realtime/transactions/get-chat.ts new file mode 100644 index 000000000..3e5fd5806 --- /dev/null +++ b/web/packages/client/src/realtime/transactions/get-chat.ts @@ -0,0 +1,43 @@ +import type { GetChatInput, InputPeer, RpcCall, RpcResult } from "@in/protocol/core" +import { Method } from "@in/protocol/core" +import type { Db } from "../../database" +import { upsertChat, upsertDialog } from "./mappers" +import { Query, type Transaction } from "./transaction" + +export type GetChatContext = { + peerId?: InputPeer +} + +export class GetChatTransaction implements Transaction { + readonly method = Method.GET_CHAT + readonly kind = Query() + readonly context: GetChatContext + + constructor(context: GetChatContext) { + this.context = context + } + + input(context: GetChatContext) { + const payload: GetChatInput = { + peerId: context.peerId, + } + + const input: RpcCall["input"] = { oneofKind: "getChat", getChat: payload } + return input + } + + async apply(result: RpcResult["result"] | undefined, db: Db) { + if (!result || result.oneofKind !== "getChat") { + throw new Error("invalid") + } + + if (result.getChat.chat) { + upsertChat(db, result.getChat.chat) + } + if (result.getChat.dialog) { + upsertDialog(db, result.getChat.dialog) + } + } +} + +export const getChat = (context: GetChatContext) => new GetChatTransaction(context) diff --git a/web/packages/client/src/realtime/transactions/get-chats.ts b/web/packages/client/src/realtime/transactions/get-chats.ts new file mode 100644 index 000000000..48421e95e --- /dev/null +++ b/web/packages/client/src/realtime/transactions/get-chats.ts @@ -0,0 +1,45 @@ +import { GetChatsInput, Method, type RpcResult } from "@in/protocol/core" +import type { Db } from "../../database" +import { Query } from "./transaction" +import type { Transaction } from "./transaction" +import { upsertChat, upsertDialog, upsertMessage, upsertUser } from "./mappers" + +export type GetChatsContext = {} + +export class GetChatsTransaction implements Transaction { + readonly method = Method.GET_CHATS + readonly kind = Query() + readonly context: GetChatsContext = {} + + input(_context: GetChatsContext): { oneofKind: "getChats"; getChats: GetChatsInput } { + return { oneofKind: "getChats", getChats: GetChatsInput.create() } + } + + async apply(result: RpcResult["result"] | undefined, db: Db) { + if (!result || result.oneofKind !== "getChats") { + throw new Error("invalid") + } + + const payload = result.getChats + + db.batch(() => { + for (const user of payload.users) { + upsertUser(db, user) + } + + for (const chat of payload.chats) { + upsertChat(db, chat) + } + + for (const dialog of payload.dialogs) { + upsertDialog(db, dialog) + } + + for (const message of payload.messages) { + upsertMessage(db, message) + } + }) + } +} + +export const getChats = () => new GetChatsTransaction() diff --git a/web/packages/client/src/realtime/transactions/get-me.ts b/web/packages/client/src/realtime/transactions/get-me.ts new file mode 100644 index 000000000..f54b1bb8e --- /dev/null +++ b/web/packages/client/src/realtime/transactions/get-me.ts @@ -0,0 +1,28 @@ +import { GetMeInput, Method, type RpcResult } from "@in/protocol/core" +import type { Db } from "../../database" +import { Query } from "./transaction" +import type { Transaction } from "./transaction" +import { upsertUser } from "./mappers" + +export type GetMeContext = {} + +export class GetMeTransaction implements Transaction { + readonly method = Method.GET_ME + readonly kind = Query() + readonly context: GetMeContext = {} + + input(_context: GetMeContext): { oneofKind: "getMe"; getMe: GetMeInput } { + return { oneofKind: "getMe", getMe: GetMeInput.create() } + } + + async apply(result: RpcResult["result"] | undefined, db: Db) { + if (!result || result.oneofKind !== "getMe") { + throw new Error("invalid") + } + if (result.getMe.user) { + upsertUser(db, result.getMe.user) + } + } +} + +export const getMe = () => new GetMeTransaction() diff --git a/web/packages/client/src/realtime/transactions/helpers.ts b/web/packages/client/src/realtime/transactions/helpers.ts new file mode 100644 index 000000000..84fa17ba4 --- /dev/null +++ b/web/packages/client/src/realtime/transactions/helpers.ts @@ -0,0 +1,30 @@ +export const toBigInt = (value: number | bigint | undefined) => { + if (value == null) return undefined + return typeof value === "bigint" ? value : BigInt(value) +} + +export const toNumber = (value: bigint | number | undefined) => { + if (value == null) return undefined + return typeof value === "bigint" ? Number(value) : value +} + +const randomUint32 = () => { + if (typeof crypto !== "undefined" && "getRandomValues" in crypto) { + const buffer = new Uint32Array(1) + crypto.getRandomValues(buffer) + return buffer[0] + } + return Math.floor(Math.random() * 2 ** 32) +} + +export const randomBigInt64 = () => { + const high = BigInt(randomUint32()) + const low = BigInt(randomUint32()) + return (high << 32n) | low +} + +let tempIdCounter = 0 +export const generateTempId = () => { + tempIdCounter = (tempIdCounter + 1) % 1000 + return -(Date.now() + tempIdCounter) +} diff --git a/web/packages/client/src/realtime/transactions/index.ts b/web/packages/client/src/realtime/transactions/index.ts new file mode 100644 index 000000000..5f4b802e4 --- /dev/null +++ b/web/packages/client/src/realtime/transactions/index.ts @@ -0,0 +1,27 @@ +export type { + Transaction, + TransactionKind, + QueryConfig, + MutationConfig, + LocalTransaction, + LocalTransactionContext, +} from "./transaction" +export { Query, Mutation } from "./transaction" +export type { TransactionError } from "./transaction-errors" +export { TransactionErrors, TransactionFailure } from "./transaction-errors" +export type { TransactionId } from "./transaction-id" +export { TransactionId as TransactionIdFactory } from "./transaction-id" +export type { TransactionWrapper } from "./transaction-wrapper" +export { Transactions } from "./transactions" +export { GetMeTransaction, getMe } from "./get-me" +export { GetChatsTransaction, getChats } from "./get-chats" +export { LogOutTransaction, logOut } from "./log-out" +export { SendMessageTransaction, sendMessage } from "./send-message" +export { EditMessageTransaction, editMessage } from "./edit-message" +export { DeleteMessagesTransaction, deleteMessages } from "./delete-messages" +export { GetChatTransaction, getChat } from "./get-chat" +export { GetChatHistoryTransaction, getChatHistory } from "./get-chat-history" +export { MarkAsUnreadTransaction, markAsUnread } from "./mark-as-unread" +export { CreateChatTransaction, createChat } from "./create-chat" +export { AddReactionTransaction, addReaction } from "./add-reaction" +export { DeleteReactionTransaction, deleteReaction } from "./delete-reaction" diff --git a/web/packages/client/src/realtime/transactions/log-out.ts b/web/packages/client/src/realtime/transactions/log-out.ts new file mode 100644 index 000000000..ad8e0a09f --- /dev/null +++ b/web/packages/client/src/realtime/transactions/log-out.ts @@ -0,0 +1,34 @@ +import { Method, type RpcCall, type RpcResult } from "@in/protocol/core" +import type { Db } from "../../database" +import { Mutation, type LocalTransaction, type LocalTransactionContext } from "./transaction" + +export type LogOutContext = { + performServerLogout?: () => Promise | void +} + +export class LogOutTransaction implements LocalTransaction { + readonly localOnly = true + readonly method = Method.UNSPECIFIED + readonly kind = Mutation() + readonly context: LogOutContext + + constructor(context: LogOutContext = {}) { + this.context = context + } + + input(): RpcCall["input"] { + return { oneofKind: undefined } + } + + async apply(_result: RpcResult["result"] | undefined, _db: Db) { + // local-only transaction; no RPC result to apply + } + + async runLocal(context: LocalTransactionContext) { + await this.context.performServerLogout?.() + await context.stopRealtime() + context.auth.logout() + } +} + +export const logOut = (context?: LogOutContext) => new LogOutTransaction(context) diff --git a/web/packages/client/src/realtime/transactions/mappers.ts b/web/packages/client/src/realtime/transactions/mappers.ts new file mode 100644 index 000000000..a2c08c338 --- /dev/null +++ b/web/packages/client/src/realtime/transactions/mappers.ts @@ -0,0 +1,129 @@ +import type { Chat, Dialog, Message, Peer, User } from "@in/protocol/core" +import type { Db } from "../../database" +import { + DbObjectKind, + type Chat as DbChat, + type Dialog as DbDialog, + type Message as DbMessage, + type User as DbUser, +} from "../../database/models" + +const toNumber = (value: bigint | number | undefined) => { + if (value == null) return undefined + return typeof value === "bigint" ? Number(value) : value +} + +const getPeerUserId = (peer?: Peer) => { + if (!peer) return undefined + if (peer.type.oneofKind === "user") { + return toNumber(peer.type.user.userId) + } + return undefined +} + +const getPeerChatId = (peer?: Peer) => { + if (!peer) return undefined + if (peer.type.oneofKind === "chat") { + return toNumber(peer.type.chat.chatId) + } + return undefined +} + +const upsert = (db: Db, object: O) => { + const ref = db.ref(object.kind as K, object.id) + const existing = db.get(ref) + if (existing) { + db.update(object) + } else { + db.insert(object) + } +} + +export const upsertUser = (db: Db, user: User) => { + const id = toNumber(user.id) + if (id == null) return + const model: DbUser = { + kind: DbObjectKind.User, + id, + firstName: user.firstName ?? undefined, + lastName: user.lastName ?? undefined, + username: user.username ?? undefined, + email: user.email ?? undefined, + min: user.min ?? undefined, + profilePhoto: user.profilePhoto + ? { + photoId: toNumber(user.profilePhoto.photoId), + fileUniqueId: user.profilePhoto.fileUniqueId ?? undefined, + cdnUrl: user.profilePhoto.cdnUrl ?? undefined, + } + : undefined, + } + upsert(db, model) +} + +export const upsertChat = (db: Db, chat: Chat) => { + const id = toNumber(chat.id) + if (id == null) return + const model: DbChat = { + kind: DbObjectKind.Chat, + id, + title: chat.title ?? undefined, + spaceId: toNumber(chat.spaceId), + emoji: chat.emoji ?? undefined, + isPublic: chat.isPublic ?? undefined, + lastMsgId: toNumber(chat.lastMsgId), + } + upsert(db, model) +} + +const getDialogId = (props: { peerUserId: number } | { peerThreadId: number }) => { + if ("peerUserId" in props) { + return props.peerUserId + } + if ("peerThreadId" in props) { + return props.peerThreadId * -1 + } +} + +export const upsertDialog = (db: Db, dialog: Dialog) => { + const chatId = toNumber(dialog.chatId) ?? getPeerChatId(dialog.peer) + if (chatId == null) return + const peerUserId = getPeerUserId(dialog.peer) + const id = peerUserId ? getDialogId({ peerUserId }) : getDialogId({ peerThreadId: chatId }) + if (id == null) return + + const model: DbDialog = { + kind: DbObjectKind.Dialog, + id, + chatId: chatId, + peerUserId, + spaceId: toNumber(dialog.spaceId), + archived: dialog.archived ?? undefined, + pinned: dialog.pinned ?? undefined, + readMaxId: toNumber(dialog.readMaxId), + unreadCount: dialog.unreadCount ?? undefined, + unreadMark: dialog.unreadMark ?? undefined, + } + upsert(db, model) +} + +export const upsertMessage = (db: Db, message: Message) => { + const id = toNumber(message.id) + if (id == null) return + // TODO: use a synthetic message ID so it doesn't change when randomId / ID is updated + const model: DbMessage = { + kind: DbObjectKind.Message, + id, + fromId: toNumber(message.fromId) ?? 0, + peerUserId: getPeerUserId(message.peerId), + chatId: toNumber(message.chatId) ?? 0, + message: message.message ?? undefined, + out: message.out, + date: toNumber(message.date), + mentioned: message.mentioned ?? undefined, + replyToMsgId: toNumber(message.replyToMsgId), + editDate: toNumber(message.editDate), + isSticker: message.isSticker ?? undefined, + } + upsert(db, model) +} diff --git a/web/packages/client/src/realtime/transactions/mark-as-unread.ts b/web/packages/client/src/realtime/transactions/mark-as-unread.ts new file mode 100644 index 000000000..592a57f2b --- /dev/null +++ b/web/packages/client/src/realtime/transactions/mark-as-unread.ts @@ -0,0 +1,38 @@ +import type { InputPeer, MarkAsUnreadInput, RpcCall, RpcResult } from "@in/protocol/core" +import { Method } from "@in/protocol/core" +import type { Db } from "../../database" +import { applyUpdates } from "../updates" +import { Mutation, type Transaction } from "./transaction" + +export type MarkAsUnreadContext = { + peerId?: InputPeer +} + +export class MarkAsUnreadTransaction implements Transaction { + readonly method = Method.MARK_AS_UNREAD + readonly kind = Mutation() + readonly context: MarkAsUnreadContext + + constructor(context: MarkAsUnreadContext) { + this.context = context + } + + input(context: MarkAsUnreadContext) { + const payload: MarkAsUnreadInput = { + peerId: context.peerId, + } + + const input: RpcCall["input"] = { oneofKind: "markAsUnread", markAsUnread: payload } + return input + } + + async apply(result: RpcResult["result"] | undefined, db: Db) { + if (!result || result.oneofKind !== "markAsUnread") { + throw new Error("invalid") + } + + applyUpdates(db, result.markAsUnread.updates) + } +} + +export const markAsUnread = (context: MarkAsUnreadContext) => new MarkAsUnreadTransaction(context) diff --git a/web/packages/client/src/realtime/transactions/send-message.ts b/web/packages/client/src/realtime/transactions/send-message.ts new file mode 100644 index 000000000..9a36bb101 --- /dev/null +++ b/web/packages/client/src/realtime/transactions/send-message.ts @@ -0,0 +1,96 @@ +import type { InputPeer, MessageEntities, RpcCall, RpcResult, SendMessageInput } from "@in/protocol/core" +import { Method } from "@in/protocol/core" +import type { Db } from "../../database" +import { DbObjectKind } from "../../database/models" +import type { AuthStore } from "../../auth" +import { applyUpdates } from "../updates" +import { Mutation, type Transaction } from "./transaction" +import { generateTempId, randomBigInt64, toBigInt } from "./helpers" + +export type SendMessageContext = { + text?: string + peerId?: InputPeer + chatId: number + replyToMsgId?: number + isSticker?: boolean + entities?: MessageEntities + randomId?: bigint + temporaryMessageId?: number +} + +export class SendMessageTransaction implements Transaction { + readonly method = Method.SEND_MESSAGE + readonly kind = Mutation() + readonly context: SendMessageContext + + constructor(context: SendMessageContext) { + this.context = { + ...context, + randomId: context.randomId ?? randomBigInt64(), + temporaryMessageId: context.temporaryMessageId ?? generateTempId(), + } + } + + input(context: SendMessageContext) { + const payload: SendMessageInput = { + peerId: context.peerId, + randomId: context.randomId, + message: context.text ?? undefined, + replyToMsgId: toBigInt(context.replyToMsgId), + temporarySendDate: BigInt(Math.floor(Date.now() / 1000)), + isSticker: context.isSticker, + entities: context.entities, + } + + const input: RpcCall["input"] = { oneofKind: "sendMessage", sendMessage: payload } + return input + } + + async optimistic(db: Db, auth: AuthStore) { + const currentUserId = auth.getState().currentUserId + if (currentUserId == null) return + + const messageId = this.context.temporaryMessageId ?? generateTempId() + + db.insert({ + kind: DbObjectKind.Message, + id: messageId, + randomId: this.context.randomId, + fromId: currentUserId, + chatId: this.context.chatId, + message: this.context.text, + out: true, + date: Math.floor(Date.now() / 1000), + replyToMsgId: this.context.replyToMsgId, + isSticker: this.context.isSticker, + }) + + const chatRef = db.ref(DbObjectKind.Chat, this.context.chatId) + const chat = db.get(chatRef) + if (chat) { + db.update({ ...chat, lastMsgId: messageId }) + } + + // TODO: reflect optimistic sending status and compose actions if needed. + } + + async apply(result: RpcResult["result"] | undefined, db: Db) { + if (!result || result.oneofKind !== "sendMessage") { + throw new Error("invalid") + } + + applyUpdates(db, result.sendMessage.updates) + } + + async failed(_error: unknown, _db: Db, _auth: AuthStore): Promise { + // TODO: mark optimistic message as failed when we track status in the DB. + } + + async cancelled(db: Db, _auth: AuthStore): Promise { + const messageId = this.context.temporaryMessageId + if (messageId == null) return + db.delete(db.ref(DbObjectKind.Message, messageId)) + } +} + +export const sendMessage = (context: SendMessageContext) => new SendMessageTransaction(context) diff --git a/web/packages/client/src/realtime/transactions/transaction-errors.ts b/web/packages/client/src/realtime/transactions/transaction-errors.ts new file mode 100644 index 000000000..4c984102c --- /dev/null +++ b/web/packages/client/src/realtime/transactions/transaction-errors.ts @@ -0,0 +1,30 @@ +export type TransactionError = + | { kind: "rpc-error"; code?: number; message?: string } + | { kind: "timeout" } + | { kind: "invalid" } + | { kind: "not-connected" } + | { kind: "stopped" } + +export class TransactionFailure extends Error { + readonly kind: TransactionError["kind"] + readonly code?: number + + constructor(error: TransactionError) { + super(error.kind) + this.name = `TransactionFailure:${error.kind}` + this.kind = error.kind + this.code = "code" in error ? error.code : undefined + } +} + +export const TransactionErrors = { + rpcError: (code?: number, message?: string): TransactionError => ({ + kind: "rpc-error", + code, + message, + }), + timeout: (): TransactionError => ({ kind: "timeout" }), + invalid: (): TransactionError => ({ kind: "invalid" }), + notConnected: (): TransactionError => ({ kind: "not-connected" }), + stopped: (): TransactionError => ({ kind: "stopped" }), +} diff --git a/web/packages/client/src/realtime/transactions/transaction-id.ts b/web/packages/client/src/realtime/transactions/transaction-id.ts new file mode 100644 index 000000000..c9aaf1757 --- /dev/null +++ b/web/packages/client/src/realtime/transactions/transaction-id.ts @@ -0,0 +1,14 @@ +export type TransactionId = string + +const generateUuid = () => { + if (typeof crypto !== "undefined" && "randomUUID" in crypto) { + return crypto.randomUUID() + } + return `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}` +} + +export const TransactionId = { + generate(): TransactionId { + return generateUuid() + }, +} diff --git a/web/packages/client/src/realtime/transactions/transaction-wrapper.ts b/web/packages/client/src/realtime/transactions/transaction-wrapper.ts new file mode 100644 index 000000000..e8e368c31 --- /dev/null +++ b/web/packages/client/src/realtime/transactions/transaction-wrapper.ts @@ -0,0 +1,14 @@ +import type { Transaction } from "./transaction" +import type { TransactionId } from "./transaction-id" + +export type TransactionWrapper = { + id: TransactionId + date: Date + transaction: Transaction +} + +export const wrapTransaction = (transaction: Transaction, id: TransactionId, date = new Date()): TransactionWrapper => ({ + id, + date, + transaction, +}) diff --git a/web/packages/client/src/realtime/transactions/transaction.ts b/web/packages/client/src/realtime/transactions/transaction.ts new file mode 100644 index 000000000..27cf5b248 --- /dev/null +++ b/web/packages/client/src/realtime/transactions/transaction.ts @@ -0,0 +1,49 @@ +import type { Method, RpcCall, RpcResult } from "@in/protocol/core" +import type { Db } from "../../database" +import type { AuthStore } from "../../auth" +import type { TransactionError } from "./transaction-errors" + +export type QueryConfig = {} + +export type MutationConfig = { + transient?: boolean +} + +export type TransactionKind = + | { kind: "query"; config: QueryConfig } + | { kind: "mutation"; config: MutationConfig } + +export const Query = (config: QueryConfig = {}): TransactionKind => ({ + kind: "query", + config, +}) + +export const Mutation = (config: MutationConfig = {}): TransactionKind => ({ + kind: "mutation", + config, +}) + +export interface Transaction { + readonly method: Method + readonly kind: TransactionKind + readonly context: Context + + input(context: Context): RpcCall["input"] + apply(result: RpcResult["result"] | undefined, db: Db): Promise + + optimistic?: (db: Db, auth: AuthStore) => Promise | void + failed?: (error: TransactionError, db: Db, auth: AuthStore) => Promise | void + cancelled?: (db: Db, auth: AuthStore) => Promise | void + describe?: () => string +} + +export type LocalTransactionContext = { + auth: AuthStore + db: Db + stopRealtime: () => Promise +} + +export interface LocalTransaction extends Transaction { + readonly localOnly: true + runLocal(context: LocalTransactionContext): Promise +} diff --git a/web/packages/client/src/realtime/transactions/transactions.ts b/web/packages/client/src/realtime/transactions/transactions.ts new file mode 100644 index 000000000..bfee8426a --- /dev/null +++ b/web/packages/client/src/realtime/transactions/transactions.ts @@ -0,0 +1,133 @@ +import { Log } from "@inline/log" +import { AsyncChannel } from "../../utils/async-channel" +import type { Transaction } from "./transaction" +import type { TransactionId } from "./transaction-id" +import { TransactionId as TransactionIdFactory } from "./transaction-id" +import type { TransactionWrapper } from "./transaction-wrapper" +import { wrapTransaction } from "./transaction-wrapper" + +export class Transactions { + readonly queueStream = new AsyncChannel() + + private queue = new Map() + private inFlight = new Map() + private sent = new Map() + private rpcMap = new Map() + + private readonly log: Log + + constructor(log?: Log) { + this.log = log ?? new Log("RealtimeV2.Transactions") + } + + enqueue(transaction: Transaction): TransactionId { + const id = TransactionIdFactory.generate() + const wrapper = wrapTransaction(transaction, id) + this.queue.set(id, wrapper) + void this.queueStream.send(undefined) + return id + } + + dequeue(): TransactionWrapper | null { + const iterator = this.queue.values().next() + if (iterator.done || !iterator.value) return null + const wrapper = iterator.value + this.queue.delete(wrapper.id) + this.inFlight.set(wrapper.id, wrapper) + return wrapper + } + + running(transactionId: TransactionId, rpcMsgId: bigint) { + const wrapper = this.inFlight.get(transactionId) + if (!wrapper) { + this.log.trace("Transaction missing when marking running", transactionId) + return + } + this.rpcMap.set(rpcMsgId, transactionId) + } + + ack(rpcMsgId: bigint) { + const transactionId = this.rpcMap.get(rpcMsgId) + if (!transactionId) return + const wrapper = this.inFlight.get(transactionId) + if (!wrapper) return + this.inFlight.delete(transactionId) + this.sent.set(transactionId, wrapper) + } + + complete(rpcMsgId: bigint): TransactionWrapper | null { + const transactionId = this.rpcMap.get(rpcMsgId) + if (!transactionId) return null + + const wrapper = this.sent.get(transactionId) ?? this.inFlight.get(transactionId) + this.sent.delete(transactionId) + this.inFlight.delete(transactionId) + this.rpcMap.delete(rpcMsgId) + + return wrapper ?? null + } + + requeue(transactionId: TransactionId) { + const wrapper = this.inFlight.get(transactionId) ?? this.sent.get(transactionId) + if (!wrapper) return + this.inFlight.delete(transactionId) + this.sent.delete(transactionId) + this.queue.set(transactionId, wrapper) + void this.queueStream.send(undefined) + } + + requeueAll() { + for (const [id, wrapper] of this.inFlight) { + this.queue.set(id, wrapper) + } + for (const [id, wrapper] of this.sent) { + this.queue.set(id, wrapper) + } + this.inFlight.clear() + this.sent.clear() + this.rpcMap.clear() + if (this.queue.size > 0) { + void this.queueStream.send(undefined) + } + } + + cancel(where: (wrapper: TransactionWrapper) => boolean): TransactionWrapper[] { + const cancelled: TransactionWrapper[] = [] + const cancelledIds = new Set() + for (const [id, wrapper] of this.queue) { + if (!where(wrapper)) continue + cancelled.push(wrapper) + cancelledIds.add(id) + this.queue.delete(id) + } + for (const [id, wrapper] of this.inFlight) { + if (!where(wrapper)) continue + cancelled.push(wrapper) + cancelledIds.add(id) + this.inFlight.delete(id) + } + for (const [id, wrapper] of this.sent) { + if (!where(wrapper)) continue + cancelled.push(wrapper) + cancelledIds.add(id) + this.sent.delete(id) + } + if (cancelledIds.size > 0) { + for (const [rpcId, transactionId] of this.rpcMap) { + if (cancelledIds.has(transactionId)) { + this.rpcMap.delete(rpcId) + } + } + } + return cancelled + } + + reset(): TransactionWrapper[] { + const wrappers = [...this.queue.values(), ...this.inFlight.values(), ...this.sent.values()] + this.queue.clear() + this.inFlight.clear() + this.sent.clear() + this.rpcMap.clear() + return wrappers + } +} diff --git a/web/packages/client/src/realtime/transport/mock-transport.ts b/web/packages/client/src/realtime/transport/mock-transport.ts new file mode 100644 index 000000000..eca68a076 --- /dev/null +++ b/web/packages/client/src/realtime/transport/mock-transport.ts @@ -0,0 +1,51 @@ +import type { ClientMessage, ServerProtocolMessage } from "@in/protocol/core" +import { AsyncChannel } from "../../utils/async-channel" +import type { TransportEvent } from "../types" +import { TransportError, type Transport } from "./transport" + +export type MockTransportState = "idle" | "connecting" | "connected" + +export class MockTransport implements Transport { + readonly events = new AsyncChannel() + readonly sent: ClientMessage[] = [] + + state: MockTransportState = "idle" + + async start() { + if (this.state !== "idle") return + this.state = "connecting" + await this.events.send({ type: "connecting" }) + } + + async stop() { + if (this.state === "idle") return + this.state = "idle" + await this.events.send({ type: "stopping" }) + } + + async send(message: ClientMessage) { + if (this.state !== "connected") { + throw TransportError.notConnected() + } + this.sent.push(message) + } + + async stopConnection() { + if (this.state === "idle") return + this.state = "connecting" + } + + async reconnect() { + this.state = "connecting" + await this.events.send({ type: "connecting" }) + } + + async connect() { + this.state = "connected" + await this.events.send({ type: "connected" }) + } + + async emitMessage(message: ServerProtocolMessage) { + await this.events.send({ type: "message", message }) + } +} diff --git a/web/packages/client/src/realtime/transport/transport.ts b/web/packages/client/src/realtime/transport/transport.ts new file mode 100644 index 000000000..9207849b8 --- /dev/null +++ b/web/packages/client/src/realtime/transport/transport.ts @@ -0,0 +1,23 @@ +import type { ClientMessage } from "@in/protocol/core" +import type { AsyncChannel } from "../../utils/async-channel" +import type { TransportEvent } from "../types" + +export class TransportError extends Error { + constructor(message: string) { + super(message) + this.name = "TransportError" + } + + static notConnected() { + return new TransportError("Transport is not connected") + } +} + +export type Transport = { + events: AsyncChannel + start: () => Promise + stop: () => Promise + send: (message: ClientMessage) => Promise + stopConnection: () => Promise + reconnect: (options?: { skipDelay?: boolean }) => Promise +} diff --git a/web/packages/client/src/realtime/transport/ws-transport.ts b/web/packages/client/src/realtime/transport/ws-transport.ts new file mode 100644 index 000000000..ecd7e90eb --- /dev/null +++ b/web/packages/client/src/realtime/transport/ws-transport.ts @@ -0,0 +1,300 @@ +import { ClientMessage, ServerProtocolMessage } from "@in/protocol/core" +import { Log, type LogLevel } from "@inline/log" +import { AsyncChannel } from "../../utils/async-channel" +import { TransportError, type Transport } from "./transport" +import type { TransportEvent } from "../types" + +const CONNECTION_TIMEOUT_MS = 10_000 +const WATCHDOG_INTERVAL_MS = 10_000 +const STUCK_CONNECTING_MS = 60_000 + +type ConnectionState = "idle" | "connecting" | "connected" + +export type WebSocketTransportOptions = { + url: string + logLevel?: LogLevel + logger?: Log +} + +export class WebSocketTransport implements Transport { + readonly events = new AsyncChannel() + + private readonly log: Log + private readonly url: string + + private state: ConnectionState = "idle" + private connectionAttemptNo = 0 + private connectingStartTime: number | null = null + + private socket: WebSocket | null = null + private reconnectionTimer: ReturnType | null = null + private connectionTimeoutTimer: ReturnType | null = null + private watchdogTimer: ReturnType | null = null + + constructor(options: WebSocketTransportOptions) { + const baseLogger = options.logger ?? new Log("RealtimeV2.WebSocketTransport", options.logLevel) + this.log = baseLogger + this.url = options.url + } + + async start() { + if (this.state !== "idle") { + this.log.error("Not starting transport because state is not idle", this.state) + return + } + + await this.setConnecting() + this.startWatchdog() + await this.openConnection() + } + + async stop() { + if (this.state === "idle") return + this.stopWatchdog() + await this.setIdle() + await this.stopConnection() + } + + async send(message: ClientMessage) { + if (this.state !== "connected" || !this.socket || this.socket.readyState !== WebSocket.OPEN) { + throw TransportError.notConnected() + } + + this.log.trace("sending message", message) + const payload = ClientMessage.toBinary(message) + this.socket.send(payload) + } + + async stopConnection() { + this.log.trace("stopping connection") + this.cleanUpPreviousConnection() + } + + async reconnect(options?: { skipDelay?: boolean }) { + const skipDelay = options?.skipDelay ?? false + + await this.setConnecting() + await this.stopConnection() + + this.connectionAttemptNo = (this.connectionAttemptNo + 1) >>> 0 + + if (this.reconnectionTimer) { + clearTimeout(this.reconnectionTimer) + this.reconnectionTimer = null + } + + const delay = this.getReconnectionDelay() + this.log.trace(`Reconnection attempt #${this.connectionAttemptNo} with ${delay}s delay`) + + this.reconnectionTimer = setTimeout(() => { + this.reconnectionTimer = null + + if (this.state === "idle" || this.state === "connected") { + this.log.debug("Not reconnecting because state is idle or connected") + return + } + + void this.openConnection() + }, skipDelay ? 0 : delay * 1000) + } + + private cleanUpPreviousConnection() { + if (this.reconnectionTimer) { + clearTimeout(this.reconnectionTimer) + this.reconnectionTimer = null + } + + this.stopConnectionTimeout() + + if (this.socket) { + this.socket.onopen = null + this.socket.onclose = null + this.socket.onerror = null + this.socket.onmessage = null + this.socket.close() + this.socket = null + } + } + + private getReconnectionDelay(): number { + const attemptNo = this.connectionAttemptNo + + if (attemptNo >= 8) { + return 7.0 + Math.random() * 4.0 + } + + return Math.min(8.0, 0.2 + Math.pow(attemptNo, 1.5) * 0.4) + } + + private startWatchdog() { + this.stopWatchdog() + this.watchdogTimer = setInterval(() => { + if (this.state !== "connecting") return + const hasNoActiveTasks = this.reconnectionTimer === null && this.connectionTimeoutTimer === null + const hasBeenConnectingTooLong = + this.connectingStartTime !== null && Date.now() - this.connectingStartTime > STUCK_CONNECTING_MS + + if (hasNoActiveTasks || hasBeenConnectingTooLong) { + this.log.debug("Watchdog: detected stuck connection, triggering recovery") + void this.handleError(new Error("Watchdog detected stuck connection")) + } + }, WATCHDOG_INTERVAL_MS) + } + + private stopWatchdog() { + if (!this.watchdogTimer) return + clearInterval(this.watchdogTimer) + this.watchdogTimer = null + } + + private startConnectionTimeout(socket: WebSocket) { + const currentAttemptNo = this.connectionAttemptNo + this.connectionTimeoutTimer = setTimeout(() => { + if (this.connectionAttemptNo !== currentAttemptNo) return + if (this.state !== "connecting") return + if (this.socket !== socket) return + + this.log.debug("Connection attempt timed out after 10 seconds") + socket.close(1006, "Connection timeout") + void this.handleError(new Error("Connection attempt timed out")) + }, CONNECTION_TIMEOUT_MS) + } + + private stopConnectionTimeout() { + if (!this.connectionTimeoutTimer) return + clearTimeout(this.connectionTimeoutTimer) + this.connectionTimeoutTimer = null + } + + private async openConnection() { + this.log.trace("Opening connection") + + if (this.state === "idle") { + this.log.debug("Not opening connection because state is idle") + return + } + + if (typeof WebSocket === "undefined") { + this.log.error("WebSocket is not available in this environment") + return + } + + this.cleanUpPreviousConnection() + await this.setConnecting() + + const socket = new WebSocket(this.url) + socket.binaryType = "arraybuffer" + this.socket = socket + + this.startConnectionTimeout(socket) + + socket.onopen = () => { + void this.connectionDidOpen(socket) + } + + socket.onmessage = (event) => { + void this.handleMessage(socket, event) + } + + socket.onclose = (event) => { + void this.handleClose(socket, event) + } + + socket.onerror = () => { + void this.handleError(new Error("WebSocket connection error"), socket) + } + } + + private async handleMessage(socket: WebSocket, event: MessageEvent) { + if (this.socket !== socket) { + this.log.trace("Ignoring message for stale WebSocket") + return + } + + const { data } = event + if (typeof data === "string") { + this.log.warn("Received string frame, expected binary data") + return + } + + try { + const payload = await this.coerceBinary(data) + const message = ServerProtocolMessage.fromBinary(payload) + await this.events.send({ type: "message", message }) + } catch (error) { + this.log.error("Failed to decode message", error) + } + } + + private async coerceBinary(data: Blob | ArrayBuffer): Promise { + if (data instanceof ArrayBuffer) { + return new Uint8Array(data) + } + + const buffer = await data.arrayBuffer() + return new Uint8Array(buffer) + } + + private async handleError(error: unknown, socket?: WebSocket) { + if (socket && this.socket !== socket) { + this.log.trace("Ignoring error for stale WebSocket", error) + return + } + + this.log.error("WebSocket connection error", error) + + if (this.state === "idle") { + this.log.trace("Ignoring error because state is idle") + return + } + + await this.setConnecting() + await this.reconnect() + } + + private async handleClose(socket: WebSocket, event: CloseEvent) { + if (this.socket !== socket) { + this.log.trace("Ignoring close for stale WebSocket") + return + } + + this.log.trace("WebSocket closed", event.code, event.reason) + await this.handleError(new Error(`WebSocket closed ${event.code} ${event.reason}`), socket) + } + + private async connectionDidOpen(socket: WebSocket) { + if (this.socket !== socket) { + this.log.trace("Ignoring didOpen for stale WebSocket") + return + } + + this.connectionAttemptNo = 0 + this.stopConnectionTimeout() + await this.setConnected() + } + + private async setConnected() { + if (this.state === "connected") return + this.state = "connected" + this.connectingStartTime = null + this.log.trace("Transport connected") + this.stopWatchdog() + await this.events.send({ type: "connected" }) + } + + private async setConnecting() { + if (this.state === "connecting") return + this.state = "connecting" + this.connectingStartTime = Date.now() + this.startWatchdog() + await this.events.send({ type: "connecting" }) + } + + private async setIdle() { + if (this.state === "idle") return + this.state = "idle" + this.connectingStartTime = null + this.log.trace("Transport stopping") + await this.events.send({ type: "stopping" }) + } +} diff --git a/web/packages/client/src/realtime/types.ts b/web/packages/client/src/realtime/types.ts new file mode 100644 index 000000000..87a190758 --- /dev/null +++ b/web/packages/client/src/realtime/types.ts @@ -0,0 +1,19 @@ +import type { RpcError, RpcResult, ServerProtocolMessage, UpdatesPayload } from "@in/protocol/core" + +export type ClientState = "connecting" | "open" + +export type TransportEvent = + | { type: "connecting" } + | { type: "connected" } + | { type: "stopping" } + | { type: "message"; message: ServerProtocolMessage } + +export type ClientEvent = + | { type: "connecting" } + | { type: "open" } + | { type: "ack"; msgId: bigint } + | { type: "rpcResult"; msgId: bigint; rpcResult: RpcResult["result"] } + | { type: "rpcError"; msgId: bigint; rpcError: RpcError } + | { type: "updates"; updates: UpdatesPayload } + +export type RealtimeConnectionState = "idle" | "connecting" | "updating" | "connected" diff --git a/web/packages/client/src/realtime/updates/apply-updates.ts b/web/packages/client/src/realtime/updates/apply-updates.ts new file mode 100644 index 000000000..0573435df --- /dev/null +++ b/web/packages/client/src/realtime/updates/apply-updates.ts @@ -0,0 +1,145 @@ +import type { Peer, Update } from "@in/protocol/core" +import type { Db } from "../../database" +import { DbObjectKind } from "../../database/models" +import { DbQueryPlanType } from "../../database/types" +import { upsertChat, upsertDialog, upsertMessage, upsertUser } from "../transactions/mappers" + +const toNumber = (value: bigint | number | undefined) => { + if (value == null) return undefined + return typeof value === "bigint" ? Number(value) : value +} + +const getPeerChatId = (peer: Peer | undefined) => { + if (!peer || peer.type.oneofKind !== "chat") return undefined + return toNumber(peer.type.chat.chatId) +} + +const getPeerUserId = (peer: Peer | undefined) => { + if (!peer || peer.type.oneofKind !== "user") return undefined + return toNumber(peer.type.user.userId) +} + +const updateDialogForPeer = (db: Db, peer: Peer | undefined, changes: { + unreadCount?: number + unreadMark?: boolean + readMaxId?: number +}) => { + if (!peer || peer.type.oneofKind === undefined) return + const dialogId = peer.type.oneofKind === "chat" ? getPeerChatId(peer) : getPeerUserId(peer) + if (dialogId == null) return + + const ref = db.ref(DbObjectKind.Dialog, dialogId) + const existing = db.get(ref) + if (!existing) return + + db.update({ + ...existing, + unreadCount: changes.unreadCount ?? existing.unreadCount, + unreadMark: changes.unreadMark ?? existing.unreadMark, + readMaxId: changes.readMaxId ?? existing.readMaxId, + }) +} + +export const applyUpdates = (db: Db, updates: Update[]) => { + db.batch(() => { + for (const update of updates) { + switch (update.update.oneofKind) { + case "newMessage": + if (update.update.newMessage.message) { + upsertMessage(db, update.update.newMessage.message) + } + break + + case "editMessage": + if (update.update.editMessage.message) { + upsertMessage(db, update.update.editMessage.message) + } + break + + case "deleteMessages": + for (const messageId of update.update.deleteMessages.messageIds) { + const id = toNumber(messageId) + if (id == null) continue + db.delete(db.ref(DbObjectKind.Message, id)) + } + break + + case "updateMessageId": { + const messageId = toNumber(update.update.updateMessageId.messageId) + if (messageId == null) break + const randomId = update.update.updateMessageId.randomId + + const matches = db.queryCollection( + DbQueryPlanType.Objects, + DbObjectKind.Message, + (message) => message.randomId === randomId, + ) + + const existing = matches[0] + if (!existing || existing.id === messageId) break + + db.delete(db.ref(DbObjectKind.Message, existing.id)) + db.insert({ + ...existing, + id: messageId, + randomId: undefined, + }) + break + } + + case "newChat": + if (update.update.newChat.chat) { + upsertChat(db, update.update.newChat.chat) + } + if (update.update.newChat.user) { + upsertUser(db, update.update.newChat.user) + } + // TODO: create or update dialog entries when server sends dialog payloads. + break + + case "deleteChat": { + const chatId = getPeerChatId(update.update.deleteChat.peerId) + if (chatId == null) break + db.delete(db.ref(DbObjectKind.Chat, chatId)) + db.delete(db.ref(DbObjectKind.Dialog, chatId)) + break + } + + case "updateReadMaxId": + updateDialogForPeer(db, update.update.updateReadMaxId.peerId, { + readMaxId: toNumber(update.update.updateReadMaxId.readMaxId), + unreadCount: update.update.updateReadMaxId.unreadCount, + }) + break + + case "markAsUnread": + updateDialogForPeer(db, update.update.markAsUnread.peerId, { + unreadMark: update.update.markAsUnread.unreadMark, + }) + break + + case "participantAdd": + case "participantDelete": + case "messageAttachment": + case "updateReaction": + case "deleteReaction": + case "spaceMemberDelete": + case "spaceMemberAdd": + case "joinSpace": + case "updateComposeAction": + case "updateUserStatus": + case "updateUserSettings": + case "newMessageNotification": + case "chatSkipPts": + case "chatHasNewUpdates": + case "spaceHasNewUpdates": + case "spaceMemberUpdate": + // TODO: apply these update kinds to local caches. + break + + default: + break + } + } + }) +} diff --git a/web/packages/client/src/realtime/updates/index.ts b/web/packages/client/src/realtime/updates/index.ts new file mode 100644 index 000000000..5f27504e7 --- /dev/null +++ b/web/packages/client/src/realtime/updates/index.ts @@ -0,0 +1 @@ +export { applyUpdates } from "./apply-updates" diff --git a/web/packages/client/src/utils/async-channel.ts b/web/packages/client/src/utils/async-channel.ts new file mode 100644 index 000000000..f65982394 --- /dev/null +++ b/web/packages/client/src/utils/async-channel.ts @@ -0,0 +1,46 @@ +type ChannelResolver = (result: IteratorResult) => void + +export class AsyncChannel implements AsyncIterable { + private queue: T[] = [] + private resolvers: ChannelResolver[] = [] + private closed = false + + async send(value: T) { + if (this.closed) return + const resolver = this.resolvers.shift() + if (resolver) { + resolver({ value, done: false }) + return + } + this.queue.push(value) + } + + close() { + if (this.closed) return + this.closed = true + for (const resolver of this.resolvers) { + resolver({ value: undefined as T, done: true }) + } + this.resolvers = [] + this.queue = [] + } + + [Symbol.asyncIterator](): AsyncIterator { + return { + next: () => { + if (this.queue.length > 0) { + const value = this.queue.shift() as T + return Promise.resolve({ value, done: false }) + } + + if (this.closed) { + return Promise.resolve({ value: undefined as T, done: true }) + } + + return new Promise>((resolve) => { + this.resolvers.push(resolve) + }) + }, + } + } +} diff --git a/web/packages/client/src/utils/emitter.ts b/web/packages/client/src/utils/emitter.ts new file mode 100644 index 000000000..3770ebbd4 --- /dev/null +++ b/web/packages/client/src/utils/emitter.ts @@ -0,0 +1,18 @@ +export type Listener = (value: T) => void + +export class Emitter { + private listeners = new Set>() + + emit(value: T) { + for (const listener of this.listeners) { + listener(value) + } + } + + subscribe(listener: Listener) { + this.listeners.add(listener) + return () => { + this.listeners.delete(listener) + } + } +} diff --git a/web/packages/client/tsconfig.json b/web/packages/client/tsconfig.json new file mode 100644 index 000000000..e0df51f6b --- /dev/null +++ b/web/packages/client/tsconfig.json @@ -0,0 +1,17 @@ +{ + "include": ["src/**/*", "vitest.config.ts"], + "compilerOptions": { + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "bundler", + "jsx": "react-jsx", + "rootDirs": ["."], + "baseUrl": ".", + "esModuleInterop": true, + "noEmit": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true + } +} diff --git a/web/packages/client/vitest.config.ts b/web/packages/client/vitest.config.ts new file mode 100644 index 000000000..8174f8ecb --- /dev/null +++ b/web/packages/client/vitest.config.ts @@ -0,0 +1,15 @@ +import path from "node:path" +import { defineConfig } from "vitest/config" + +export default defineConfig({ + resolve: { + alias: { + "@in/protocol": path.resolve(__dirname, "../protocol/src"), + "@inline/log": path.resolve(__dirname, "../log/src/index.ts"), + "@inline/config": path.resolve(__dirname, "../config/src/index.ts"), + }, + }, + test: { + include: ["src/**/*.test.ts", "src/**/*.test.tsx"], + }, +}) diff --git a/web/packages/config/package.json b/web/packages/config/package.json new file mode 100644 index 000000000..4c3571b93 --- /dev/null +++ b/web/packages/config/package.json @@ -0,0 +1,16 @@ +{ + "name": "@inline/config", + "private": true, + "version": "0.0.0", + "type": "module", + "types": "./src/index.ts", + "exports": { + ".": { + "types": "./src/index.ts", + "default": "./src/index.ts" + } + }, + "devDependencies": { + "typescript": "^5.7.2" + } +} diff --git a/web/packages/config/src/index.ts b/web/packages/config/src/index.ts new file mode 100644 index 000000000..d50053dec --- /dev/null +++ b/web/packages/config/src/index.ts @@ -0,0 +1,68 @@ +export type InlineConfig = { + serverUrl: string + apiBaseUrl: string + realtimeUrl: string +} + +export type InlineConfigOverrides = Partial + +const isProd = () => { + const viteProd = typeof import.meta !== "undefined" && Boolean(import.meta.env?.PROD) + const nodeProd = typeof process !== "undefined" && process.env.NODE_ENV === "production" + return viteProd || nodeProd +} + +const normalizeUrl = (url: string) => url.replace(/\/+$/, "") + +const toWebSocketUrl = (url: string) => { + if (url.startsWith("https://")) return `wss://${url.slice("https://".length)}` + if (url.startsWith("http://")) return `ws://${url.slice("http://".length)}` + return url +} + +const resolveEnv = (key: string) => { + if (typeof import.meta !== "undefined") { + const envValue = (import.meta as { env?: Record }).env?.[key] + if (envValue) return envValue + } + + if (typeof process !== "undefined" && process.env[key]) { + return process.env[key] + } + + return undefined +} + +const resolveDefaultServerUrl = () => { + const envUrl = resolveEnv("VITE_SERVER_URL") + if (envUrl) return normalizeUrl(envUrl) + return isProd() ? "https://api.inline.chat" : "http://localhost:8000" +} + +const deriveRealtimeUrl = (serverUrl: string) => `${toWebSocketUrl(normalizeUrl(serverUrl))}/realtime` + +const resolveConfig = (overrides: InlineConfigOverrides = {}): InlineConfig => { + const envServerUrl = resolveEnv("VITE_SERVER_URL") + const envApiBaseUrl = resolveEnv("VITE_API_BASE_URL") + const envRealtimeUrl = resolveEnv("VITE_REALTIME_URL") + + const serverUrl = normalizeUrl(overrides.serverUrl ?? envServerUrl ?? resolveDefaultServerUrl()) + const apiBaseUrl = normalizeUrl(overrides.apiBaseUrl ?? envApiBaseUrl ?? `${serverUrl}/v1`) + const realtimeUrl = normalizeUrl(overrides.realtimeUrl ?? envRealtimeUrl ?? deriveRealtimeUrl(serverUrl)) + + return { serverUrl, apiBaseUrl, realtimeUrl } +} + +let overrides: InlineConfigOverrides = {} + +export const getConfig = () => resolveConfig(overrides) + +export const setConfig = (next: InlineConfigOverrides) => { + overrides = { ...overrides, ...next } +} + +export const getServerUrl = () => getConfig().serverUrl +export const getApiBaseUrl = () => getConfig().apiBaseUrl +export const getRealtimeUrl = () => getConfig().realtimeUrl + +export const resolveRealtimeUrl = (serverUrl = getServerUrl()) => deriveRealtimeUrl(serverUrl) diff --git a/web/packages/config/tsconfig.json b/web/packages/config/tsconfig.json new file mode 100644 index 000000000..166c62fed --- /dev/null +++ b/web/packages/config/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "composite": true, + "rootDir": ".", + "noUncheckedIndexedAccess": false + }, + "include": ["src"] +} diff --git a/web/packages/log/package.json b/web/packages/log/package.json new file mode 100644 index 000000000..049cce4fd --- /dev/null +++ b/web/packages/log/package.json @@ -0,0 +1,16 @@ +{ + "name": "@inline/log", + "private": true, + "version": "0.0.0", + "type": "module", + "types": "./src/index.ts", + "exports": { + ".": { + "types": "./src/index.ts", + "default": "./src/index.ts" + } + }, + "devDependencies": { + "typescript": "^5.7.2" + } +} diff --git a/web/packages/log/src/index.ts b/web/packages/log/src/index.ts new file mode 100644 index 000000000..5998d9ec2 --- /dev/null +++ b/web/packages/log/src/index.ts @@ -0,0 +1,70 @@ +export type LogLevel = "error" | "warn" | "info" | "debug" | "trace" + +const levelOrder: Record = { + error: 0, + warn: 1, + info: 2, + debug: 3, + trace: 4, +} + +export type LogSink = { + error: (...args: unknown[]) => void + warn: (...args: unknown[]) => void + info: (...args: unknown[]) => void + debug: (...args: unknown[]) => void + trace: (...args: unknown[]) => void +} + +const defaultSink: LogSink = { + error: (...args) => console.error(...args), + warn: (...args) => console.warn(...args), + info: (...args) => console.info(...args), + debug: (...args) => console.debug(...args), + trace: (...args) => console.debug(...args), +} + +export class Log { + private scope: string + private level: LogLevel + private sink: LogSink + + constructor(scope: string, level: LogLevel = "info", sink: LogSink = defaultSink) { + this.scope = scope + this.level = level + this.sink = sink + } + + withLevel(level: LogLevel) { + return new Log(this.scope, level, this.sink) + } + + withScope(scope: string) { + return new Log(`${this.scope}.${scope}`, this.level, this.sink) + } + + trace(...args: unknown[]) { + this.log("trace", ...args) + } + + debug(...args: unknown[]) { + this.log("debug", ...args) + } + + info(...args: unknown[]) { + this.log("info", ...args) + } + + warn(...args: unknown[]) { + this.log("warn", ...args) + } + + error(...args: unknown[]) { + this.log("error", ...args) + } + + private log(level: LogLevel, ...args: unknown[]) { + if (levelOrder[level] > levelOrder[this.level]) return + this.sink[level](`[${this.scope}]`, ...args) + } +} diff --git a/web/packages/log/tsconfig.json b/web/packages/log/tsconfig.json new file mode 100644 index 000000000..166c62fed --- /dev/null +++ b/web/packages/log/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "composite": true, + "rootDir": ".", + "noUncheckedIndexedAccess": false + }, + "include": ["src"] +} diff --git a/web/packages/protocol/package.json b/web/packages/protocol/package.json new file mode 100644 index 000000000..3e0764660 --- /dev/null +++ b/web/packages/protocol/package.json @@ -0,0 +1,27 @@ +{ + "name": "@in/protocol", + "private": true, + "version": "0.0.0", + "type": "module", + "types": "./src/index.ts", + "exports": { + ".": { + "types": "./src/index.ts", + "default": "./src/index.ts" + }, + "./core": { + "types": "./src/core.ts", + "default": "./src/core.ts" + }, + "./client": { + "types": "./src/client.ts", + "default": "./src/client.ts" + } + }, + "dependencies": { + "@protobuf-ts/runtime": "^2.11.1" + }, + "devDependencies": { + "typescript": "^5.7.2" + } +} diff --git a/web/packages/protocol/src/client.ts b/web/packages/protocol/src/client.ts new file mode 100644 index 000000000..b3a859cb1 --- /dev/null +++ b/web/packages/protocol/src/client.ts @@ -0,0 +1,92 @@ +// @generated by protobuf-ts 2.9.4 +// @generated from protobuf file "client.proto" (package "client", syntax proto3) +// tslint:disable +import type { BinaryWriteOptions } from "@protobuf-ts/runtime"; +import type { IBinaryWriter } from "@protobuf-ts/runtime"; +import { WireType } from "@protobuf-ts/runtime"; +import type { BinaryReadOptions } from "@protobuf-ts/runtime"; +import type { IBinaryReader } from "@protobuf-ts/runtime"; +import { UnknownFieldHandler } from "@protobuf-ts/runtime"; +import type { PartialMessage } from "@protobuf-ts/runtime"; +import { reflectionMergePartial } from "@protobuf-ts/runtime"; +import { MessageType } from "@protobuf-ts/runtime"; +import { RpcCall } from "./core"; +/** + * @generated from protobuf message client.Transaction + */ +export interface Transaction { + /** + * @generated from protobuf field: string id = 1; + */ + id: string; + /** + * @generated from protobuf field: int64 date = 2; + */ + date: bigint; + /** + * @generated from protobuf field: RpcCall call = 3; + */ + call?: RpcCall; +} +// @generated message type with reflection information, may provide speed optimized methods +class Transaction$Type extends MessageType { + constructor() { + super("client.Transaction", [ + { no: 1, name: "id", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 2, name: "date", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 3, name: "call", kind: "message", T: () => RpcCall } + ]); + } + create(value?: PartialMessage): Transaction { + const message = globalThis.Object.create((this.messagePrototype!)); + message.id = ""; + message.date = 0n; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Transaction): Transaction { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* string id */ 1: + message.id = reader.string(); + break; + case /* int64 date */ 2: + message.date = reader.int64().toBigInt(); + break; + case /* RpcCall call */ 3: + message.call = RpcCall.internalBinaryRead(reader, reader.uint32(), options, message.call); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: Transaction, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* string id = 1; */ + if (message.id !== "") + writer.tag(1, WireType.LengthDelimited).string(message.id); + /* int64 date = 2; */ + if (message.date !== 0n) + writer.tag(2, WireType.Varint).int64(message.date); + /* RpcCall call = 3; */ + if (message.call) + RpcCall.internalBinaryWrite(message.call, writer.tag(3, WireType.LengthDelimited).fork(), options).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message client.Transaction + */ +export const Transaction = new Transaction$Type(); diff --git a/web/packages/protocol/src/core.ts b/web/packages/protocol/src/core.ts new file mode 100644 index 000000000..2c646a48a --- /dev/null +++ b/web/packages/protocol/src/core.ts @@ -0,0 +1,12589 @@ +// @generated by protobuf-ts 2.9.4 +// @generated from protobuf file "core.proto" (syntax proto3) +// tslint:disable +import type { BinaryWriteOptions } from "@protobuf-ts/runtime"; +import type { IBinaryWriter } from "@protobuf-ts/runtime"; +import { WireType } from "@protobuf-ts/runtime"; +import type { BinaryReadOptions } from "@protobuf-ts/runtime"; +import type { IBinaryReader } from "@protobuf-ts/runtime"; +import { UnknownFieldHandler } from "@protobuf-ts/runtime"; +import type { PartialMessage } from "@protobuf-ts/runtime"; +import { reflectionMergePartial } from "@protobuf-ts/runtime"; +import { MessageType } from "@protobuf-ts/runtime"; +// --- Protocol Messages --- + +/** + * @generated from protobuf message ClientMessage + */ +export interface ClientMessage { + /** + * @generated from protobuf field: uint64 id = 1; + */ + id: bigint; + /** + * @generated from protobuf field: uint32 seq = 2; + */ + seq: number; + /** + * @generated from protobuf oneof: body + */ + body: { + oneofKind: "connectionInit"; + /** + * @generated from protobuf field: ConnectionInit connection_init = 4; + */ + connectionInit: ConnectionInit; + } | { + oneofKind: "rpcCall"; + /** + * @generated from protobuf field: RpcCall rpc_call = 5; + */ + rpcCall: RpcCall; + } | { + oneofKind: "ack"; + /** + * @generated from protobuf field: Ack ack = 6; + */ + ack: Ack; + } | { + oneofKind: "ping"; + /** + * @generated from protobuf field: Ping ping = 7; + */ + ping: Ping; + } | { + oneofKind: undefined; + }; +} +/** + * @generated from protobuf message ConnectionInit + */ +export interface ConnectionInit { + /** + * @generated from protobuf field: string token = 1; + */ + token: string; + /** + * Build number of the client app + * + * @generated from protobuf field: optional int32 build_number = 2; + */ + buildNumber?: number; + /** + * API layer, for specific API differentiation + * + * @generated from protobuf field: optional uint32 layer = 3; + */ + layer?: number; +} +/** + * @generated from protobuf message ServerProtocolMessage + */ +export interface ServerProtocolMessage { + /** + * @generated from protobuf field: uint64 id = 1; + */ + id: bigint; + /** + * @generated from protobuf oneof: body + */ + body: { + oneofKind: "connectionOpen"; + /** + * @generated from protobuf field: ConnectionOpen connection_open = 4; + */ + connectionOpen: ConnectionOpen; + } | { + oneofKind: "rpcResult"; + /** + * @generated from protobuf field: RpcResult rpc_result = 5; + */ + rpcResult: RpcResult; + } | { + oneofKind: "rpcError"; + /** + * @generated from protobuf field: RpcError rpc_error = 6; + */ + rpcError: RpcError; + } | { + oneofKind: "message"; + /** + * @generated from protobuf field: ServerMessage message = 7; + */ + message: ServerMessage; + } | { + oneofKind: "ack"; + /** + * @generated from protobuf field: Ack ack = 8; + */ + ack: Ack; + } | { + oneofKind: "pong"; + /** + * @generated from protobuf field: Pong pong = 9; + */ + pong: Pong; + } | { + oneofKind: "connectionError"; + /** + * @generated from protobuf field: ConnectionError connection_error = 10; + */ + connectionError: ConnectionError; + } | { + oneofKind: undefined; + }; +} +/** + * @generated from protobuf message ServerMessage + */ +export interface ServerMessage { + /** + * @generated from protobuf oneof: payload + */ + payload: { + oneofKind: "update"; + /** + * @generated from protobuf field: UpdatesPayload update = 4; + */ + update: UpdatesPayload; + } | { + oneofKind: undefined; + }; +} +/** + * @generated from protobuf message UpdatesPayload + */ +export interface UpdatesPayload { + /** + * @generated from protobuf field: repeated Update updates = 1; + */ + updates: Update[]; +} +/** + * @generated from protobuf message Ack + */ +export interface Ack { + /** + * @generated from protobuf field: uint64 msg_id = 1; + */ + msgId: bigint; +} +/** + * @generated from protobuf message ConnectionOpen + */ +export interface ConnectionOpen { +} +/** + * @generated from protobuf message ConnectionError + */ +export interface ConnectionError { +} +/** + * @generated from protobuf message Ping + */ +export interface Ping { + /** + * @generated from protobuf field: uint64 nonce = 1; + */ + nonce: bigint; +} +/** + * @generated from protobuf message Pong + */ +export interface Pong { + /** + * @generated from protobuf field: uint64 nonce = 1; + */ + nonce: bigint; +} +// --- Application Types --- + +/** + * @generated from protobuf message InputPeer + */ +export interface InputPeer { + /** + * @generated from protobuf oneof: type + */ + type: { + oneofKind: "self"; + /** + * @generated from protobuf field: InputPeerSelf self = 2; + */ + self: InputPeerSelf; + } | { + oneofKind: "chat"; + /** + * @generated from protobuf field: InputPeerChat chat = 3; + */ + chat: InputPeerChat; + } | { + oneofKind: "user"; + /** + * @generated from protobuf field: InputPeerUser user = 4; + */ + user: InputPeerUser; + } | { + oneofKind: undefined; + }; +} +/** + * @generated from protobuf message InputPeerSelf + */ +export interface InputPeerSelf { +} +/** + * @generated from protobuf message InputPeerChat + */ +export interface InputPeerChat { + /** + * @generated from protobuf field: int64 chat_id = 1; + */ + chatId: bigint; +} +/** + * @generated from protobuf message InputPeerUser + */ +export interface InputPeerUser { + /** + * @generated from protobuf field: int64 user_id = 1; + */ + userId: bigint; +} +/** + * @generated from protobuf message Peer + */ +export interface Peer { + /** + * @generated from protobuf oneof: type + */ + type: { + oneofKind: "chat"; + /** + * @generated from protobuf field: PeerChat chat = 2; + */ + chat: PeerChat; + } | { + oneofKind: "user"; + /** + * @generated from protobuf field: PeerUser user = 3; + */ + user: PeerUser; + } | { + oneofKind: undefined; + }; +} +/** + * @generated from protobuf message PeerChat + */ +export interface PeerChat { + /** + * @generated from protobuf field: int64 chat_id = 1; + */ + chatId: bigint; +} +/** + * @generated from protobuf message PeerUser + */ +export interface PeerUser { + /** + * @generated from protobuf field: int64 user_id = 1; + */ + userId: bigint; +} +/** + * @generated from protobuf message User + */ +export interface User { + /** + * @generated from protobuf field: int64 id = 1; + */ + id: bigint; + /** + * @generated from protobuf field: optional string first_name = 2; + */ + firstName?: string; + /** + * @generated from protobuf field: optional string last_name = 3; + */ + lastName?: string; + /** + * @generated from protobuf field: optional string username = 4; + */ + username?: string; + /** + * @generated from protobuf field: optional string phone_number = 5; + */ + phoneNumber?: string; + /** + * @generated from protobuf field: optional string email = 6; + */ + email?: string; + /** + * If true, certain fields such as email or phone_number will be missing + * + * @generated from protobuf field: optional bool min = 7; + */ + min?: boolean; + /** + * @generated from protobuf field: optional UserStatus status = 8; + */ + status?: UserStatus; + /** + * @generated from protobuf field: optional UserProfilePhoto profile_photo = 9; + */ + profilePhoto?: UserProfilePhoto; + // Last message ID + // optional int64 last_msg_id = 10; + + /** + * If true, the user has not completed the setup process + * + * @generated from protobuf field: optional bool pending_setup = 11; + */ + pendingSetup?: boolean; + /** + * @generated from protobuf field: optional string time_zone = 12; + */ + timeZone?: string; + /** + * @generated from protobuf field: optional bool bot = 13; + */ + bot?: boolean; +} +/** + * @generated from protobuf message UserProfilePhoto + */ +export interface UserProfilePhoto { + /** + * ID of the photo + * + * @generated from protobuf field: optional int64 photo_id = 1; + */ + photoId?: bigint; + /** + * Stripped thumbnail of the photo + * + * @generated from protobuf field: optional bytes stripped_thumb = 2; + */ + strippedThumb?: Uint8Array; + /** + * Photo + * + * @generated from protobuf field: optional string cdn_url = 3; + */ + cdnUrl?: string; + /** + * Unique identifier of the file for cache invalidation + * + * @generated from protobuf field: optional string file_unique_id = 4; + */ + fileUniqueId?: string; +} +/** + * @generated from protobuf message Dialog + */ +export interface Dialog { + /** + * @generated from protobuf field: Peer peer = 1; + */ + peer?: Peer; + /** + * @generated from protobuf field: optional int64 space_id = 2; + */ + spaceId?: bigint; + /** + * @generated from protobuf field: optional bool archived = 3; + */ + archived?: boolean; + /** + * @generated from protobuf field: optional bool pinned = 4; + */ + pinned?: boolean; + /** + * @generated from protobuf field: optional int64 read_max_id = 5; + */ + readMaxId?: bigint; + /** + * @generated from protobuf field: optional int32 unread_count = 6; + */ + unreadCount?: number; + /** + * @generated from protobuf field: optional int64 chat_id = 7; + */ + chatId?: bigint; + /** + * @generated from protobuf field: optional bool unread_mark = 8; + */ + unreadMark?: boolean; +} +/** + * A thread + * + * @generated from protobuf message Chat + */ +export interface Chat { + /** + * @generated from protobuf field: int64 id = 1; + */ + id: bigint; + /** + * Title + * + * @generated from protobuf field: string title = 2; + */ + title: string; + /** + * If it belongs to a space + * + * @generated from protobuf field: optional int64 space_id = 3; + */ + spaceId?: bigint; + /** + * Optional description + * + * @generated from protobuf field: optional string description = 4; + */ + description?: string; + /** + * Emoji to show as the icon, can be null + * + * @generated from protobuf field: optional string emoji = 5; + */ + emoji?: string; + /** + * If true, everyone in parent space can accces it + * + * @generated from protobuf field: optional bool is_public = 6; + */ + isPublic?: boolean; + /** + * Last message ID + * + * @generated from protobuf field: optional int64 last_msg_id = 7; + */ + lastMsgId?: bigint; + /** + * ID of the peer that this chat belongs to + * + * @generated from protobuf field: Peer peer_id = 8; + */ + peerId?: Peer; + /** + * Date of creation + * + * @generated from protobuf field: optional int64 date = 9; + */ + date?: bigint; +} +/** + * @generated from protobuf message Message + */ +export interface Message { + /** + * @generated from protobuf field: int64 id = 1; + */ + id: bigint; + /** + * User ID of the sender + * + * @generated from protobuf field: int64 from_id = 2; + */ + fromId: bigint; + /** + * Peer ID of the recipient + * + * @generated from protobuf field: Peer peer_id = 3; + */ + peerId?: Peer; + /** + * The "chat ID" of the message, for messages in a chat (deprecated) + * + * @generated from protobuf field: int64 chat_id = 4; + */ + chatId: bigint; + /** + * Message text + * + * @generated from protobuf field: optional string message = 5; + */ + message?: string; + /** + * Whether the message is outgoing + * + * @generated from protobuf field: bool out = 6; + */ + out: boolean; + /** + * Date of the message + * + * @generated from protobuf field: int64 date = 7; + */ + date: bigint; + /** + * Whether user is mentioned + * + * @generated from protobuf field: optional bool mentioned = 8; + */ + mentioned?: boolean; + /** + * Message ID of the message being replied to + * + * @generated from protobuf field: optional int64 reply_to_msg_id = 9; + */ + replyToMsgId?: bigint; + /** + * Media of the message + * + * @generated from protobuf field: optional MessageMedia media = 10; + */ + media?: MessageMedia; + /** + * Date of the last edit if edited + * + * @generated from protobuf field: optional int64 edit_date = 11; + */ + editDate?: bigint; + /** + * ID of the grouped message if it's part of an album + * + * @generated from protobuf field: optional int64 grouped_id = 12; + */ + groupedId?: bigint; + /** + * Attachments of the message + * + * @generated from protobuf field: optional MessageAttachments attachments = 13; + */ + attachments?: MessageAttachments; + /** + * Reactions of the message + * + * @generated from protobuf field: optional MessageReactions reactions = 14; + */ + reactions?: MessageReactions; + /** + * Whether the message is a sticker + * + * @generated from protobuf field: optional bool is_sticker = 15; + */ + isSticker?: boolean; + /** + * Rich text entities + * + * @generated from protobuf field: optional MessageEntities entities = 16; + */ + entities?: MessageEntities; +} +/** + * @generated from protobuf message MessageEntities + */ +export interface MessageEntities { + /** + * @generated from protobuf field: repeated MessageEntity entities = 1; + */ + entities: MessageEntity[]; +} +/** + * @generated from protobuf message MessageEntity + */ +export interface MessageEntity { + /** + * @generated from protobuf field: MessageEntity.Type type = 1; + */ + type: MessageEntity_Type; + /** + * @generated from protobuf field: int64 offset = 2; + */ + offset: bigint; + /** + * @generated from protobuf field: int64 length = 3; + */ + length: bigint; + /** + * @generated from protobuf oneof: entity + */ + entity: { + oneofKind: "mention"; + /** + * @generated from protobuf field: MessageEntity.MessageEntityMention mention = 4; + */ + mention: MessageEntity_MessageEntityMention; + } | { + oneofKind: "textUrl"; + /** + * @generated from protobuf field: MessageEntity.MessageEntityTextUrl text_url = 5; + */ + textUrl: MessageEntity_MessageEntityTextUrl; + } | { + oneofKind: "pre"; + /** + * @generated from protobuf field: MessageEntity.MessageEntityPre pre = 6; + */ + pre: MessageEntity_MessageEntityPre; + } | { + oneofKind: undefined; + }; +} +/** + * @generated from protobuf message MessageEntity.MessageEntityMention + */ +export interface MessageEntity_MessageEntityMention { + /** + * @generated from protobuf field: int64 user_id = 1; + */ + userId: bigint; +} +/** + * @generated from protobuf message MessageEntity.MessageEntityTextUrl + */ +export interface MessageEntity_MessageEntityTextUrl { + /** + * @generated from protobuf field: string url = 1; + */ + url: string; +} +/** + * @generated from protobuf message MessageEntity.MessageEntityPre + */ +export interface MessageEntity_MessageEntityPre { + /** + * @generated from protobuf field: string language = 1; + */ + language: string; +} +/** + * @generated from protobuf enum MessageEntity.Type + */ +export enum MessageEntity_Type { + /** + * @generated from protobuf enum value: TYPE_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + /** + * @generated from protobuf enum value: TYPE_MENTION = 1; + */ + MENTION = 1, + /** + * @generated from protobuf enum value: TYPE_URL = 2; + */ + URL = 2, + /** + * @generated from protobuf enum value: TYPE_TEXT_URL = 3; + */ + TEXT_URL = 3, + /** + * @generated from protobuf enum value: TYPE_EMAIL = 4; + */ + EMAIL = 4, + /** + * @generated from protobuf enum value: TYPE_BOLD = 5; + */ + BOLD = 5, + /** + * @generated from protobuf enum value: TYPE_ITALIC = 6; + */ + ITALIC = 6, + /** + * @generated from protobuf enum value: TYPE_USERNAME_MENTION = 7; + */ + USERNAME_MENTION = 7, + /** + * @generated from protobuf enum value: TYPE_CODE = 8; + */ + CODE = 8, + /** + * @generated from protobuf enum value: TYPE_PRE = 9; + */ + PRE = 9 +} +/** + * @generated from protobuf message MessageReactions + */ +export interface MessageReactions { + /** + * Reactions of the message + * + * @generated from protobuf field: repeated Reaction reactions = 1; + */ + reactions: Reaction[]; +} +/** + * @generated from protobuf message Reaction + */ +export interface Reaction { + /** + * Emoji of the reaction + * + * @generated from protobuf field: string emoji = 1; + */ + emoji: string; + /** + * ID of the user who reacted + * + * @generated from protobuf field: int64 user_id = 2; + */ + userId: bigint; + /** + * ID of the message that this reaction is for + * + * @generated from protobuf field: int64 message_id = 3; + */ + messageId: bigint; + /** + * ID of the chat that this reaction is for + * + * @generated from protobuf field: int64 chat_id = 4; + */ + chatId: bigint; + /** + * Date of the reaction + * + * @generated from protobuf field: int64 date = 5; + */ + date: bigint; +} +/** + * @generated from protobuf message Member + */ +export interface Member { + /** + * @generated from protobuf field: int64 id = 1; + */ + id: bigint; + /** + * @generated from protobuf field: int64 space_id = 2; + */ + spaceId: bigint; + /** + * @generated from protobuf field: int64 user_id = 3; + */ + userId: bigint; + /** + * @generated from protobuf field: optional Member.Role role = 4; + */ + role?: Member_Role; + /** + * Date of joining + * + * @generated from protobuf field: int64 date = 5; + */ + date: bigint; + /** + * Whether member can access public chats in the space + * + * @generated from protobuf field: bool can_access_public_chats = 6; + */ + canAccessPublicChats: boolean; +} +/** + * @generated from protobuf enum Member.Role + */ +export enum Member_Role { + /** + * @generated from protobuf enum value: OWNER = 0; + */ + OWNER = 0, + /** + * @generated from protobuf enum value: ADMIN = 1; + */ + ADMIN = 1, + /** + * @generated from protobuf enum value: MEMBER = 2; + */ + MEMBER = 2 +} +/** + * @generated from protobuf message Space + */ +export interface Space { + /** + * ID + * + * @generated from protobuf field: int64 id = 1; + */ + id: bigint; + /** + * Name of the space + * + * @generated from protobuf field: string name = 2; + */ + name: string; + /** + * Whether the current user is the creator of the space + * + * @generated from protobuf field: bool creator = 3; + */ + creator: boolean; + /** + * Date of creation + * + * @generated from protobuf field: int64 date = 4; + */ + date: bigint; +} +/** + * Add reaction input + * + * @generated from protobuf message AddReactionInput + */ +export interface AddReactionInput { + /** + * Emoji of the reaction + * + * @generated from protobuf field: string emoji = 1; + */ + emoji: string; + /** + * ID of the message that this reaction is for + * + * @generated from protobuf field: int64 message_id = 2; + */ + messageId: bigint; + /** + * ID of the peer that this reaction is for + * + * @generated from protobuf field: InputPeer peer_id = 3; + */ + peerId?: InputPeer; +} +/** + * Add reaction result + * + * @generated from protobuf message AddReactionResult + */ +export interface AddReactionResult { + /** + * @generated from protobuf field: repeated Update updates = 1; + */ + updates: Update[]; +} +/** + * Delete reaction input + * + * @generated from protobuf message DeleteReactionInput + */ +export interface DeleteReactionInput { + /** + * @generated from protobuf field: string emoji = 1; + */ + emoji: string; + /** + * @generated from protobuf field: InputPeer peer_id = 2; + */ + peerId?: InputPeer; + /** + * @generated from protobuf field: int64 message_id = 3; + */ + messageId: bigint; +} +/** + * @generated from protobuf message DeleteReactionResult + */ +export interface DeleteReactionResult { + /** + * @generated from protobuf field: repeated Update updates = 1; + */ + updates: Update[]; +} +/** + * @generated from protobuf message MessageAttachments + */ +export interface MessageAttachments { + /** + * @generated from protobuf field: repeated MessageAttachment attachments = 1; + */ + attachments: MessageAttachment[]; +} +/** + * @generated from protobuf message MessageAttachment + */ +export interface MessageAttachment { + /** + * ID + * + * @generated from protobuf field: int64 id = 4; + */ + id: bigint; + /** + * @generated from protobuf oneof: attachment + */ + attachment: { + oneofKind: "externalTask"; + /** + * @generated from protobuf field: MessageAttachmentExternalTask external_task = 2; + */ + externalTask: MessageAttachmentExternalTask; + } | { + oneofKind: "urlPreview"; + /** + * @generated from protobuf field: UrlPreview url_preview = 3; + */ + urlPreview: UrlPreview; + } | { + oneofKind: undefined; + }; +} +/** + * @generated from protobuf message UrlPreview + */ +export interface UrlPreview { + /** + * ID of external task in our database + * + * @generated from protobuf field: int64 id = 1; + */ + id: bigint; + /** + * URL of the link + * + * @generated from protobuf field: optional string url = 2; + */ + url?: string; + /** + * Site name of the link + * + * @generated from protobuf field: optional string site_name = 3; + */ + siteName?: string; + /** + * Title of the link + * + * @generated from protobuf field: optional string title = 4; + */ + title?: string; + /** + * Description of the link + * + * @generated from protobuf field: optional string description = 5; + */ + description?: string; + /** + * Image ID of the link + * + * @generated from protobuf field: optional Photo photo = 6; + */ + photo?: Photo; + /** + * Duration of the content + * + * @generated from protobuf field: optional int64 duration = 7; + */ + duration?: bigint; +} +/** + * @generated from protobuf message MessageAttachmentExternalTask + */ +export interface MessageAttachmentExternalTask { + /** + * ID of external task in our database + * + * @generated from protobuf field: int64 id = 1; + */ + id: bigint; + /** + * ID of the task in the external application + * + * @generated from protobuf field: string task_id = 2; + */ + taskId: string; + /** + * Application name + * + * @generated from protobuf field: string application = 3; + */ + application: string; + /** + * Title of the task/issue + * + * @generated from protobuf field: string title = 4; + */ + title: string; + /** + * Status of the task + * + * @generated from protobuf field: MessageAttachmentExternalTask.Status status = 5; + */ + status: MessageAttachmentExternalTask_Status; + /** + * Assigned user ID in Inline + * + * @generated from protobuf field: int64 assigned_user_id = 6; + */ + assignedUserId: bigint; + /** + * URL of the task/issue in the external application + * + * @generated from protobuf field: string url = 7; + */ + url: string; + /** + * Number/code of the task/issue in the external application + * + * @generated from protobuf field: string number = 8; + */ + number: string; + /** + * Date of creation/addition in Inline + * + * @generated from protobuf field: int64 date = 9; + */ + date: bigint; +} +/** + * @generated from protobuf enum MessageAttachmentExternalTask.Status + */ +export enum MessageAttachmentExternalTask_Status { + /** + * @generated from protobuf enum value: STATUS_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + /** + * @generated from protobuf enum value: STATUS_BACKLOG = 1; + */ + BACKLOG = 1, + /** + * @generated from protobuf enum value: STATUS_TODO = 2; + */ + TODO = 2, + /** + * @generated from protobuf enum value: STATUS_IN_PROGRESS = 3; + */ + IN_PROGRESS = 3, + /** + * @generated from protobuf enum value: STATUS_DONE = 4; + */ + DONE = 4, + /** + * @generated from protobuf enum value: STATUS_CANCELLED = 5; + */ + CANCELLED = 5 +} +/** + * WIP: add document, audio, video. + * + * @generated from protobuf message MessageMedia + */ +export interface MessageMedia { + /** + * @generated from protobuf oneof: media + */ + media: { + oneofKind: "photo"; + /** + * @generated from protobuf field: MessagePhoto photo = 1; + */ + photo: MessagePhoto; + } | { + oneofKind: "video"; + /** + * @generated from protobuf field: MessageVideo video = 2; + */ + video: MessageVideo; + } | { + oneofKind: "document"; + /** + * @generated from protobuf field: MessageDocument document = 3; + */ + document: MessageDocument; + } | { + oneofKind: undefined; + }; +} +/** + * @generated from protobuf message MessagePhoto + */ +export interface MessagePhoto { + /** + * @generated from protobuf field: Photo photo = 1; + */ + photo?: Photo; +} +/** + * @generated from protobuf message MessageVideo + */ +export interface MessageVideo { + /** + * @generated from protobuf field: Video video = 1; + */ + video?: Video; +} +/** + * @generated from protobuf message MessageDocument + */ +export interface MessageDocument { + /** + * @generated from protobuf field: Document document = 1; + */ + document?: Document; +} +/** + * @generated from protobuf message Video + */ +export interface Video { + /** + * @generated from protobuf field: int64 id = 1; + */ + id: bigint; + /** + * Date of upload + * + * @generated from protobuf field: int64 date = 2; + */ + date: bigint; + /** + * Width of the video + * + * @generated from protobuf field: int32 w = 3; + */ + w: number; + /** + * Height of the video + * + * @generated from protobuf field: int32 h = 4; + */ + h: number; + /** + * Duration of the video in seconds + * + * @generated from protobuf field: int32 duration = 5; + */ + duration: number; + /** + * File size + * + * @generated from protobuf field: int32 size = 6; + */ + size: number; + /** + * Thumbnail of the video + * + * @generated from protobuf field: optional Photo photo = 7; + */ + photo?: Photo; + /** + * CDN URL + * + * @generated from protobuf field: optional string cdn_url = 8; + */ + cdnUrl?: string; +} +/** + * @generated from protobuf message Document + */ +export interface Document { + /** + * @generated from protobuf field: int64 id = 1; + */ + id: bigint; + /** + * Original file name + * + * @generated from protobuf field: string file_name = 2; + */ + fileName: string; + /** + * MIME type of the file + * + * @generated from protobuf field: string mime_type = 3; + */ + mimeType: string; + /** + * File size + * + * @generated from protobuf field: int32 size = 4; + */ + size: number; + /** + * CDN URL + * + * @generated from protobuf field: optional string cdn_url = 5; + */ + cdnUrl?: string; + /** + * Date of upload + * + * @generated from protobuf field: int64 date = 6; + */ + date: bigint; +} +/** + * Photo for message media, profile photo, space photo, or chat photo + * + * @generated from protobuf message Photo + */ +export interface Photo { + /** + * ID + * + * @generated from protobuf field: int64 id = 1; + */ + id: bigint; + /** + * Date of upload + * + * @generated from protobuf field: int64 date = 2; + */ + date: bigint; + /** + * @generated from protobuf field: repeated PhotoSize sizes = 3; + */ + sizes: PhotoSize[]; + /** + * Format of the photo + * + * @generated from protobuf field: Photo.Format format = 4; + */ + format: Photo_Format; + /** + * Unique identifier of the file + * + * @generated from protobuf field: optional string file_unique_id = 100; + */ + fileUniqueId?: string; +} +/** + * @generated from protobuf enum Photo.Format + */ +export enum Photo_Format { + /** + * @generated from protobuf enum value: FORMAT_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + /** + * @generated from protobuf enum value: FORMAT_JPEG = 1; + */ + JPEG = 1, + /** + * @generated from protobuf enum value: FORMAT_PNG = 2; + */ + PNG = 2 +} +/** + * @generated from protobuf message PhotoSize + */ +export interface PhotoSize { + /** + * * Thumbnail type. + * Currently supported: + * - "b" - small box 140x140 + * - "c" - medium box 320x320 + * - "d" - regular box 800x800 + * - "f" - large box 2560x2560 + * - "s" - stripped (tiny version embedded in bytes) + * - "y" - ?? + * - "x" - small cropped ?? + * - "w" - medium cropped ?? + * - "v" - ?? + * + * @generated from protobuf field: string type = 1; + */ + type: string; + /** + * Width in pixels + * + * @generated from protobuf field: int32 w = 2; + */ + w: number; + /** + * Height in pixels + * + * @generated from protobuf field: int32 h = 3; + */ + h: number; + /** + * File size + * + * @generated from protobuf field: int32 size = 4; + */ + size: number; + /** + * Bytes for stripped size used in blur thumbnails + * + * @generated from protobuf field: optional bytes bytes = 5; + */ + bytes?: Uint8Array; + /** + * CDN URL + * + * @generated from protobuf field: optional string cdn_url = 6; + */ + cdnUrl?: string; +} +// --- Application RPC Functions --- + +/** + * @generated from protobuf message RpcError + */ +export interface RpcError { + /** + * @generated from protobuf field: uint64 req_msg_id = 1; + */ + reqMsgId: bigint; + /** + * @generated from protobuf field: RpcError.Code error_code = 2; + */ + errorCode: RpcError_Code; + /** + * @generated from protobuf field: string message = 3; + */ + message: string; + /** + * @generated from protobuf field: int32 code = 4; + */ + code: number; +} +/** + * Type of error + * + * @generated from protobuf enum RpcError.Code + */ +export enum RpcError_Code { + /** + * @generated from protobuf enum value: UNKNOWN = 0; + */ + UNKNOWN = 0, + /** + * @generated from protobuf enum value: BAD_REQUEST = 1; + */ + BAD_REQUEST = 1, + /** + * @generated from protobuf enum value: UNAUTHENTICATED = 2; + */ + UNAUTHENTICATED = 2, + /** + * @generated from protobuf enum value: RATE_LIMIT = 3; + */ + RATE_LIMIT = 3, + /** + * @generated from protobuf enum value: INTERNAL_ERROR = 4; + */ + INTERNAL_ERROR = 4, + /** + * @generated from protobuf enum value: PEER_ID_INVALID = 5; + */ + PEER_ID_INVALID = 5, + /** + * @generated from protobuf enum value: MESSAGE_ID_INVALID = 6; + */ + MESSAGE_ID_INVALID = 6, + /** + * @generated from protobuf enum value: USER_ID_INVALID = 7; + */ + USER_ID_INVALID = 7, + /** + * @generated from protobuf enum value: USER_ALREADY_MEMBER = 8; + */ + USER_ALREADY_MEMBER = 8, + /** + * @generated from protobuf enum value: SPACE_ID_INVALID = 9; + */ + SPACE_ID_INVALID = 9, + /** + * @generated from protobuf enum value: CHAT_ID_INVALID = 10; + */ + CHAT_ID_INVALID = 10, + /** + * @generated from protobuf enum value: EMAIL_INVALID = 11; + */ + EMAIL_INVALID = 11, + /** + * @generated from protobuf enum value: PHONE_NUMBER_INVALID = 12; + */ + PHONE_NUMBER_INVALID = 12, + /** + * @generated from protobuf enum value: SPACE_ADMIN_REQUIRED = 13; + */ + SPACE_ADMIN_REQUIRED = 13, + /** + * @generated from protobuf enum value: SPACE_OWNER_REQUIRED = 14; + */ + SPACE_OWNER_REQUIRED = 14 +} +/** + * @generated from protobuf message RpcCall + */ +export interface RpcCall { + /** + * @generated from protobuf field: Method method = 1; + */ + method: Method; + /** + * @generated from protobuf oneof: input + */ + input: { + oneofKind: "getMe"; + /** + * @generated from protobuf field: GetMeInput getMe = 2; + */ + getMe: GetMeInput; + } | { + oneofKind: "getPeerPhoto"; + /** + * @generated from protobuf field: GetPeerPhotoInput getPeerPhoto = 3; + */ + getPeerPhoto: GetPeerPhotoInput; + } | { + oneofKind: "deleteMessages"; + /** + * @generated from protobuf field: DeleteMessagesInput deleteMessages = 4; + */ + deleteMessages: DeleteMessagesInput; + } | { + oneofKind: "sendMessage"; + /** + * @generated from protobuf field: SendMessageInput sendMessage = 5; + */ + sendMessage: SendMessageInput; + } | { + oneofKind: "getChatHistory"; + /** + * @generated from protobuf field: GetChatHistoryInput getChatHistory = 6; + */ + getChatHistory: GetChatHistoryInput; + } | { + oneofKind: "addReaction"; + /** + * @generated from protobuf field: AddReactionInput addReaction = 7; + */ + addReaction: AddReactionInput; + } | { + oneofKind: "deleteReaction"; + /** + * @generated from protobuf field: DeleteReactionInput deleteReaction = 8; + */ + deleteReaction: DeleteReactionInput; + } | { + oneofKind: "editMessage"; + /** + * @generated from protobuf field: EditMessageInput editMessage = 9; + */ + editMessage: EditMessageInput; + } | { + oneofKind: "createChat"; + /** + * @generated from protobuf field: CreateChatInput createChat = 10; + */ + createChat: CreateChatInput; + } | { + oneofKind: "getSpaceMembers"; + /** + * @generated from protobuf field: GetSpaceMembersInput getSpaceMembers = 11; + */ + getSpaceMembers: GetSpaceMembersInput; + } | { + oneofKind: "deleteChat"; + /** + * @generated from protobuf field: DeleteChatInput deleteChat = 12; + */ + deleteChat: DeleteChatInput; + } | { + oneofKind: "inviteToSpace"; + /** + * @generated from protobuf field: InviteToSpaceInput inviteToSpace = 13; + */ + inviteToSpace: InviteToSpaceInput; + } | { + oneofKind: "getChatParticipants"; + /** + * @generated from protobuf field: GetChatParticipantsInput getChatParticipants = 14; + */ + getChatParticipants: GetChatParticipantsInput; + } | { + oneofKind: "addChatParticipant"; + /** + * @generated from protobuf field: AddChatParticipantInput addChatParticipant = 15; + */ + addChatParticipant: AddChatParticipantInput; + } | { + oneofKind: "removeChatParticipant"; + /** + * @generated from protobuf field: RemoveChatParticipantInput removeChatParticipant = 16; + */ + removeChatParticipant: RemoveChatParticipantInput; + } | { + oneofKind: "translateMessages"; + /** + * @generated from protobuf field: TranslateMessagesInput translateMessages = 17; + */ + translateMessages: TranslateMessagesInput; + } | { + oneofKind: "getChats"; + /** + * @generated from protobuf field: GetChatsInput getChats = 18; + */ + getChats: GetChatsInput; + } | { + oneofKind: "updateUserSettings"; + /** + * @generated from protobuf field: UpdateUserSettingsInput updateUserSettings = 19; + */ + updateUserSettings: UpdateUserSettingsInput; + } | { + oneofKind: "getUserSettings"; + /** + * @generated from protobuf field: GetUserSettingsInput getUserSettings = 20; + */ + getUserSettings: GetUserSettingsInput; + } | { + oneofKind: "sendComposeAction"; + /** + * @generated from protobuf field: SendComposeActionInput sendComposeAction = 21; + */ + sendComposeAction: SendComposeActionInput; + } | { + oneofKind: "createBot"; + /** + * @generated from protobuf field: CreateBotInput createBot = 22; + */ + createBot: CreateBotInput; + } | { + oneofKind: "deleteMember"; + /** + * @generated from protobuf field: DeleteMemberInput deleteMember = 23; + */ + deleteMember: DeleteMemberInput; + } | { + oneofKind: "markAsUnread"; + /** + * @generated from protobuf field: MarkAsUnreadInput markAsUnread = 24; + */ + markAsUnread: MarkAsUnreadInput; + } | { + oneofKind: "getUpdatesState"; + /** + * @generated from protobuf field: GetUpdatesStateInput getUpdatesState = 25; + */ + getUpdatesState: GetUpdatesStateInput; + } | { + oneofKind: "getChat"; + /** + * @generated from protobuf field: GetChatInput getChat = 26; + */ + getChat: GetChatInput; + } | { + oneofKind: "getUpdates"; + /** + * @generated from protobuf field: GetUpdatesInput getUpdates = 27; + */ + getUpdates: GetUpdatesInput; + } | { + oneofKind: "updateMemberAccess"; + /** + * @generated from protobuf field: UpdateMemberAccessInput updateMemberAccess = 28; + */ + updateMemberAccess: UpdateMemberAccessInput; + } | { + oneofKind: undefined; + }; +} +/** + * @generated from protobuf message RpcResult + */ +export interface RpcResult { + /** + * @generated from protobuf field: uint64 req_msg_id = 1; + */ + reqMsgId: bigint; + /** + * @generated from protobuf oneof: result + */ + result: { + oneofKind: "getMe"; + /** + * @generated from protobuf field: GetMeResult getMe = 2; + */ + getMe: GetMeResult; + } | { + oneofKind: "getPeerPhoto"; + /** + * @generated from protobuf field: GetPeerPhotoResult getPeerPhoto = 3; + */ + getPeerPhoto: GetPeerPhotoResult; + } | { + oneofKind: "deleteMessages"; + /** + * @generated from protobuf field: DeleteMessagesResult deleteMessages = 4; + */ + deleteMessages: DeleteMessagesResult; + } | { + oneofKind: "sendMessage"; + /** + * @generated from protobuf field: SendMessageResult sendMessage = 5; + */ + sendMessage: SendMessageResult; + } | { + oneofKind: "getChatHistory"; + /** + * @generated from protobuf field: GetChatHistoryResult getChatHistory = 6; + */ + getChatHistory: GetChatHistoryResult; + } | { + oneofKind: "addReaction"; + /** + * @generated from protobuf field: AddReactionResult addReaction = 7; + */ + addReaction: AddReactionResult; + } | { + oneofKind: "deleteReaction"; + /** + * @generated from protobuf field: DeleteReactionResult deleteReaction = 8; + */ + deleteReaction: DeleteReactionResult; + } | { + oneofKind: "editMessage"; + /** + * @generated from protobuf field: EditMessageResult editMessage = 9; + */ + editMessage: EditMessageResult; + } | { + oneofKind: "createChat"; + /** + * @generated from protobuf field: CreateChatResult createChat = 10; + */ + createChat: CreateChatResult; + } | { + oneofKind: "getSpaceMembers"; + /** + * @generated from protobuf field: GetSpaceMembersResult getSpaceMembers = 11; + */ + getSpaceMembers: GetSpaceMembersResult; + } | { + oneofKind: "deleteChat"; + /** + * @generated from protobuf field: DeleteChatResult deleteChat = 12; + */ + deleteChat: DeleteChatResult; + } | { + oneofKind: "inviteToSpace"; + /** + * @generated from protobuf field: InviteToSpaceResult inviteToSpace = 13; + */ + inviteToSpace: InviteToSpaceResult; + } | { + oneofKind: "getChatParticipants"; + /** + * @generated from protobuf field: GetChatParticipantsResult getChatParticipants = 14; + */ + getChatParticipants: GetChatParticipantsResult; + } | { + oneofKind: "addChatParticipant"; + /** + * @generated from protobuf field: AddChatParticipantResult addChatParticipant = 15; + */ + addChatParticipant: AddChatParticipantResult; + } | { + oneofKind: "removeChatParticipant"; + /** + * @generated from protobuf field: RemoveChatParticipantResult removeChatParticipant = 16; + */ + removeChatParticipant: RemoveChatParticipantResult; + } | { + oneofKind: "translateMessages"; + /** + * @generated from protobuf field: TranslateMessagesResult translateMessages = 17; + */ + translateMessages: TranslateMessagesResult; + } | { + oneofKind: "getChats"; + /** + * @generated from protobuf field: GetChatsResult getChats = 18; + */ + getChats: GetChatsResult; + } | { + oneofKind: "updateUserSettings"; + /** + * @generated from protobuf field: UpdateUserSettingsResult updateUserSettings = 19; + */ + updateUserSettings: UpdateUserSettingsResult; + } | { + oneofKind: "getUserSettings"; + /** + * @generated from protobuf field: GetUserSettingsResult getUserSettings = 20; + */ + getUserSettings: GetUserSettingsResult; + } | { + oneofKind: "sendComposeAction"; + /** + * @generated from protobuf field: SendComposeActionResult sendComposeAction = 21; + */ + sendComposeAction: SendComposeActionResult; + } | { + oneofKind: "createBot"; + /** + * @generated from protobuf field: CreateBotResult createBot = 22; + */ + createBot: CreateBotResult; + } | { + oneofKind: "deleteMember"; + /** + * @generated from protobuf field: DeleteMemberResult deleteMember = 23; + */ + deleteMember: DeleteMemberResult; + } | { + oneofKind: "markAsUnread"; + /** + * @generated from protobuf field: MarkAsUnreadResult markAsUnread = 24; + */ + markAsUnread: MarkAsUnreadResult; + } | { + oneofKind: "getUpdatesState"; + /** + * @generated from protobuf field: GetUpdatesStateResult getUpdatesState = 25; + */ + getUpdatesState: GetUpdatesStateResult; + } | { + oneofKind: "getChat"; + /** + * @generated from protobuf field: GetChatResult getChat = 26; + */ + getChat: GetChatResult; + } | { + oneofKind: "getUpdates"; + /** + * @generated from protobuf field: GetUpdatesResult getUpdates = 27; + */ + getUpdates: GetUpdatesResult; + } | { + oneofKind: "updateMemberAccess"; + /** + * @generated from protobuf field: UpdateMemberAccessResult updateMemberAccess = 28; + */ + updateMemberAccess: UpdateMemberAccessResult; + } | { + oneofKind: undefined; + }; +} +/** + * @generated from protobuf message UpdateBucket + */ +export interface UpdateBucket { + /** + * @generated from protobuf oneof: type + */ + type: { + oneofKind: "user"; + /** + * @generated from protobuf field: UpdateBucketUser user = 1; + */ + user: UpdateBucketUser; + } | { + oneofKind: "space"; + /** + * @generated from protobuf field: UpdateBucketSpace space = 2; + */ + space: UpdateBucketSpace; + } | { + oneofKind: "chat"; + /** + * @generated from protobuf field: UpdateBucketChat chat = 3; + */ + chat: UpdateBucketChat; + } | { + oneofKind: undefined; + }; +} +/** + * @generated from protobuf message UpdateBucketUser + */ +export interface UpdateBucketUser { +} +/** + * @generated from protobuf message UpdateBucketSpace + */ +export interface UpdateBucketSpace { + /** + * @generated from protobuf field: int64 space_id = 1; + */ + spaceId: bigint; +} +/** + * @generated from protobuf message UpdateBucketChat + */ +export interface UpdateBucketChat { + /** + * @generated from protobuf field: InputPeer peer_id = 1; + */ + peerId?: InputPeer; +} +/** + * @generated from protobuf message GetUpdatesInput + */ +export interface GetUpdatesInput { + /** + * @generated from protobuf field: UpdateBucket bucket = 1; + */ + bucket?: UpdateBucket; + /** + * @generated from protobuf field: int64 start_seq = 2; + */ + startSeq: bigint; + // unsure about these + // int64 start_date = 3; + + /** + * if the difference with the start_seq is greater than this, the result will + * contain "too long" result type + * + * @generated from protobuf field: int32 total_limit = 3; + */ + totalLimit: number; +} +/** + * @generated from protobuf message GetUpdatesResult + */ +export interface GetUpdatesResult { + /** + * @generated from protobuf field: repeated Update updates = 1; + */ + updates: Update[]; + /** + * Final sequence of the updates slice + * + * @generated from protobuf field: int64 seq = 2; + */ + seq: bigint; + /** + * Final date of the updates slice + * + * @generated from protobuf field: int64 date = 3; + */ + date: bigint; + /** + * Whether this is the final slice of updates + * + * @generated from protobuf field: optional bool final = 4; + */ + final?: boolean; + /** + * Type of the result + * + * @generated from protobuf field: GetUpdatesResult.ResultType result_type = 5; + */ + resultType: GetUpdatesResult_ResultType; +} +/** + * @generated from protobuf enum GetUpdatesResult.ResultType + */ +export enum GetUpdatesResult_ResultType { + /** + * @generated from protobuf enum value: RESULT_TYPE_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + /** + * @generated from protobuf enum value: RESULT_TYPE_EMPTY = 1; + */ + EMPTY = 1, + /** + * @generated from protobuf enum value: RESULT_TYPE_SLICE = 2; + */ + SLICE = 2, + /** + * @generated from protobuf enum value: RESULT_TYPE_TOO_LONG = 3; + */ + TOO_LONG = 3 +} +/** + * Remove member from space + * + * @generated from protobuf message DeleteMemberInput + */ +export interface DeleteMemberInput { + /** + * Space ID + * + * @generated from protobuf field: int64 space_id = 1; + */ + spaceId: bigint; + /** + * Member ID + * + * @generated from protobuf field: int64 user_id = 2; + */ + userId: bigint; +} +/** + * Remove member from space result + * + * @generated from protobuf message DeleteMemberResult + */ +export interface DeleteMemberResult { + /** + * @generated from protobuf field: repeated Update updates = 1; + */ + updates: Update[]; +} +/** + * Update an existing member's access/role within a space + * + * @generated from protobuf message UpdateMemberAccessInput + */ +export interface UpdateMemberAccessInput { + /** + * Space ID + * + * @generated from protobuf field: int64 space_id = 1; + */ + spaceId: bigint; + /** + * Member user ID + * + * @generated from protobuf field: int64 user_id = 2; + */ + userId: bigint; + /** + * Updated role/options + * + * @generated from protobuf field: SpaceMemberRole role = 3; + */ + role?: SpaceMemberRole; +} +/** + * @generated from protobuf message UpdateMemberAccessResult + */ +export interface UpdateMemberAccessResult { + /** + * @generated from protobuf field: repeated Update updates = 1; + */ + updates: Update[]; +} +/** + * @generated from protobuf message GetUpdatesStateInput + */ +export interface GetUpdatesStateInput { + /** + * Local date of state + * + * @generated from protobuf field: int64 date = 2; + */ + date: bigint; +} +/** + * @generated from protobuf message GetUpdatesStateResult + */ +export interface GetUpdatesStateResult { + /** + * Current date of the state + * + * @generated from protobuf field: int64 date = 1; + */ + date: bigint; +} +/** + * @generated from protobuf message GetChatInput + */ +export interface GetChatInput { + /** + * Peer ID to get chat for + * + * @generated from protobuf field: InputPeer peer_id = 1; + */ + peerId?: InputPeer; +} +/** + * @generated from protobuf message GetChatResult + */ +export interface GetChatResult { + /** + * @generated from protobuf field: Chat chat = 1; + */ + chat?: Chat; + /** + * @generated from protobuf field: Dialog dialog = 2; + */ + dialog?: Dialog; +} +/** + * Mark dialog as unread + * + * @generated from protobuf message MarkAsUnreadInput + */ +export interface MarkAsUnreadInput { + /** + * Peer ID to mark as unread + * + * @generated from protobuf field: InputPeer peer_id = 1; + */ + peerId?: InputPeer; +} +/** + * Mark dialog as unread result + * + * @generated from protobuf message MarkAsUnreadResult + */ +export interface MarkAsUnreadResult { + /** + * @generated from protobuf field: repeated Update updates = 1; + */ + updates: Update[]; +} +/** + * @generated from protobuf message CreateBotInput + */ +export interface CreateBotInput { + /** + * Name of the bot + * + * @generated from protobuf field: string name = 1; + */ + name: string; + /** + * Username of the bot + * + * @generated from protobuf field: string username = 2; + */ + username: string; + /** + * If not null, add the bot to this space + * + * @generated from protobuf field: optional int64 add_to_space = 3; + */ + addToSpace?: bigint; +} +/** + * @generated from protobuf message CreateBotResult + */ +export interface CreateBotResult { + /** + * @generated from protobuf field: User bot = 1; + */ + bot?: User; + /** + * Token to use for the bot + * + * @generated from protobuf field: string token = 2; + */ + token: string; +} +/** + * @generated from protobuf message GetUserSettingsInput + */ +export interface GetUserSettingsInput { +} +/** + * @generated from protobuf message GetUserSettingsResult + */ +export interface GetUserSettingsResult { + /** + * @generated from protobuf field: UserSettings user_settings = 1; + */ + userSettings?: UserSettings; +} +/** + * @generated from protobuf message UserSettings + */ +export interface UserSettings { + /** + * @generated from protobuf field: optional NotificationSettings notification_settings = 1; + */ + notificationSettings?: NotificationSettings; +} +/** + * @generated from protobuf message NotificationSettings + */ +export interface NotificationSettings { + /** + * @generated from protobuf field: optional NotificationSettings.Mode mode = 1; + */ + mode?: NotificationSettings_Mode; + /** + * If true, no sound will be played for notifications + * + * @generated from protobuf field: optional bool silent = 2; + */ + silent?: boolean; + /** + * If true, the notification requires mentioning the user + * + * @generated from protobuf field: optional bool zen_mode_requires_mention = 3; + */ + zenModeRequiresMention?: boolean; + /** + * If true, the default rules will be used + * + * @generated from protobuf field: optional bool zen_mode_uses_default_rules = 4; + */ + zenModeUsesDefaultRules?: boolean; + /** + * Custom rules for notifications + * + * @generated from protobuf field: optional string zen_mode_custom_rules = 5; + */ + zenModeCustomRules?: string; +} +/** + * @generated from protobuf enum NotificationSettings.Mode + */ +export enum NotificationSettings_Mode { + /** + * @generated from protobuf enum value: MODE_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + /** + * @generated from protobuf enum value: MODE_ALL = 1; + */ + ALL = 1, + /** + * @generated from protobuf enum value: MODE_NONE = 2; + */ + NONE = 2, + /** + * @generated from protobuf enum value: MODE_MENTIONS = 3; + */ + MENTIONS = 3, + /** + * @generated from protobuf enum value: MODE_IMPORTANT_ONLY = 4; + */ + IMPORTANT_ONLY = 4 +} +/** + * @generated from protobuf message UpdateUserSettingsInput + */ +export interface UpdateUserSettingsInput { + /** + * @generated from protobuf field: UserSettings user_settings = 1; + */ + userSettings?: UserSettings; +} +/** + * @generated from protobuf message UpdateUserSettingsResult + */ +export interface UpdateUserSettingsResult { + /** + * @generated from protobuf field: repeated Update updates = 1; + */ + updates: Update[]; +} +/** + * @generated from protobuf message SendComposeActionInput + */ +export interface SendComposeActionInput { + /** + * Peer - where user is typing/uploading + * + * @generated from protobuf field: InputPeer peer_id = 1; + */ + peerId?: InputPeer; + /** + * Compose action (optional, null means stop action) + * + * @generated from protobuf field: optional UpdateComposeAction.ComposeAction action = 2; + */ + action?: UpdateComposeAction_ComposeAction; +} +/** + * @generated from protobuf message SendComposeActionResult + */ +export interface SendComposeActionResult { +} +/** + * @generated from protobuf message GetChatsInput + */ +export interface GetChatsInput { +} +/** + * @generated from protobuf message GetChatsResult + */ +export interface GetChatsResult { + /** + * Dialogs + * + * @generated from protobuf field: repeated Dialog dialogs = 1; + */ + dialogs: Dialog[]; + /** + * Chats + * + * @generated from protobuf field: repeated Chat chats = 2; + */ + chats: Chat[]; + /** + * Spaces referenced in the chats + * + * @generated from protobuf field: repeated Space spaces = 3; + */ + spaces: Space[]; + /** + * Users referenced in the chats or messages + * + * @generated from protobuf field: repeated User users = 4; + */ + users: User[]; + /** + * Messages referenced in the chats + * + * @generated from protobuf field: repeated Message messages = 5; + */ + messages: Message[]; +} +/** + * @generated from protobuf message TranslateMessagesInput + */ +export interface TranslateMessagesInput { + /** + * ID of the peer + * + * @generated from protobuf field: InputPeer peer_id = 1; + */ + peerId?: InputPeer; + /** + * IDs of the messages to translate, these must not have gaps of more than 50 + * messages + * + * @generated from protobuf field: repeated int64 message_ids = 2; + */ + messageIds: bigint[]; + // // Only return messages starting from the specified message ID + // int32 offset_id = 2; + + // // Number of messages to return + // int32 limit = 3; + + /** + * Language code to translate to + * + * @generated from protobuf field: string language = 4; + */ + language: string; +} +/** + * @generated from protobuf message TranslateMessagesResult + */ +export interface TranslateMessagesResult { + /** + * Translated messages + * + * @generated from protobuf field: repeated MessageTranslation translations = 1; + */ + translations: MessageTranslation[]; +} +/** + * @generated from protobuf message MessageTranslation + */ +export interface MessageTranslation { + /** + * ID of the message + * + * @generated from protobuf field: int64 message_id = 1; + */ + messageId: bigint; + /** + * Language code of the translation + * + * @generated from protobuf field: string language = 2; + */ + language: string; + /** + * Translation of the message + * + * @generated from protobuf field: string translation = 3; + */ + translation: string; + /** + * Date of translation + * + * @generated from protobuf field: int64 date = 4; + */ + date: bigint; + /** + * Entities in the translation + * + * @generated from protobuf field: optional MessageEntities entities = 5; + */ + entities?: MessageEntities; +} +/** + * @generated from protobuf message GetMeInput + */ +export interface GetMeInput { +} +/** + * @generated from protobuf message GetMeResult + */ +export interface GetMeResult { + /** + * @generated from protobuf field: User user = 1; + */ + user?: User; +} +/** + * @generated from protobuf message GetPeerPhotoInput + */ +export interface GetPeerPhotoInput { + /** + * @generated from protobuf field: InputPeer peer_id = 1; + */ + peerId?: InputPeer; + /** + * @generated from protobuf field: int64 photo_id = 2; + */ + photoId: bigint; +} +/** + * @generated from protobuf message GetPeerPhotoResult + */ +export interface GetPeerPhotoResult { + /** + * @generated from protobuf field: Photo photo = 1; + */ + photo?: Photo; +} +/** + * @generated from protobuf message DeleteMessagesInput + */ +export interface DeleteMessagesInput { + /** + * @generated from protobuf field: repeated int64 message_ids = 1; + */ + messageIds: bigint[]; + /** + * @generated from protobuf field: InputPeer peer_id = 2; + */ + peerId?: InputPeer; +} +/** + * @generated from protobuf message DeleteMessagesResult + */ +export interface DeleteMessagesResult { + /** + * @generated from protobuf field: repeated Update updates = 1; + */ + updates: Update[]; +} +/** + * @generated from protobuf message EditMessageInput + */ +export interface EditMessageInput { + /** + * @generated from protobuf field: int64 message_id = 1; + */ + messageId: bigint; + /** + * @generated from protobuf field: InputPeer peer_id = 2; + */ + peerId?: InputPeer; + /** + * @generated from protobuf field: string text = 3; + */ + text: string; + /** + * @generated from protobuf field: optional MessageEntities entities = 7; + */ + entities?: MessageEntities; +} +/** + * @generated from protobuf message EditMessageResult + */ +export interface EditMessageResult { + /** + * @generated from protobuf field: repeated Update updates = 1; + */ + updates: Update[]; +} +/** + * @generated from protobuf message InputMedia + */ +export interface InputMedia { + /** + * @generated from protobuf oneof: media + */ + media: { + oneofKind: "photo"; + /** + * @generated from protobuf field: InputMediaPhoto photo = 1; + */ + photo: InputMediaPhoto; + } | { + oneofKind: "video"; + /** + * @generated from protobuf field: InputMediaVideo video = 2; + */ + video: InputMediaVideo; + } | { + oneofKind: "document"; + /** + * @generated from protobuf field: InputMediaDocument document = 3; + */ + document: InputMediaDocument; + } | { + oneofKind: undefined; + }; +} +/** + * @generated from protobuf message InputMediaPhoto + */ +export interface InputMediaPhoto { + /** + * ID of the photo that we have uploaded + * + * @generated from protobuf field: int64 photo_id = 1; + */ + photoId: bigint; +} +/** + * @generated from protobuf message InputMediaVideo + */ +export interface InputMediaVideo { + /** + * ID of the video that we have uploaded + * + * @generated from protobuf field: int64 video_id = 1; + */ + videoId: bigint; +} +/** + * @generated from protobuf message InputMediaDocument + */ +export interface InputMediaDocument { + /** + * ID of the document that we have uploaded + * + * @generated from protobuf field: int64 document_id = 1; + */ + documentId: bigint; +} +/** + * @generated from protobuf message SendMessageInput + */ +export interface SendMessageInput { + /** + * @generated from protobuf field: InputPeer peer_id = 1; + */ + peerId?: InputPeer; + /** + * Message text or caption + * + * @generated from protobuf field: optional string message = 2; + */ + message?: string; + /** + * Message ID of the message being replied to + * + * @generated from protobuf field: optional int64 reply_to_msg_id = 3; + */ + replyToMsgId?: bigint; + /** + * Random ID to prevent duplicate messages + * + * @generated from protobuf field: optional int64 random_id = 4; + */ + randomId?: bigint; + /** + * Media to send + * + * @generated from protobuf field: optional InputMedia media = 5; + */ + media?: InputMedia; + /** + * Date of sending (until we fix the client reordering) + * + * @generated from protobuf field: optional int64 temporary_send_date = 1000; + */ + temporarySendDate?: bigint; + /** + * Whether the message is a sticker + * + * @generated from protobuf field: optional bool is_sticker = 6; + */ + isSticker?: boolean; + /** + * Entities in the message (bold, italic, mention, etc) + * + * @generated from protobuf field: optional MessageEntities entities = 7; + */ + entities?: MessageEntities; +} +/** + * @generated from protobuf message SendMessageResult + */ +export interface SendMessageResult { + /** + * @generated from protobuf field: repeated Update updates = 2; + */ + updates: Update[]; +} +/** + * @generated from protobuf message GetChatHistoryInput + */ +export interface GetChatHistoryInput { + /** + * @generated from protobuf field: InputPeer peer_id = 1; + */ + peerId?: InputPeer; + /** + * ID of the message to start from + * + * @generated from protobuf field: optional int64 offset_id = 2; + */ + offsetId?: bigint; + /** + * Number of messages to return + * + * @generated from protobuf field: optional int32 limit = 3; + */ + limit?: number; +} +/** + * @generated from protobuf message GetChatHistoryResult + */ +export interface GetChatHistoryResult { + /** + * @generated from protobuf field: repeated Message messages = 1; + */ + messages: Message[]; +} +/** + * @generated from protobuf message InputChatParticipant + */ +export interface InputChatParticipant { + /** + * @generated from protobuf field: int64 user_id = 1; + */ + userId: bigint; +} +/** + * @generated from protobuf message CreateChatInput + */ +export interface CreateChatInput { + /** + * Required title + * + * @generated from protobuf field: string title = 1; + */ + title: string; + /** + * Parent space ID + * + * @generated from protobuf field: optional int64 space_id = 2; + */ + spaceId?: bigint; + /** + * Optional description of the thread + * + * @generated from protobuf field: optional string description = 3; + */ + description?: string; + /** + * Emoji to show as the icon, can be null + * + * @generated from protobuf field: optional string emoji = 4; + */ + emoji?: string; + /** + * If true, everyone in parent space can accces it + * + * @generated from protobuf field: bool is_public = 5; + */ + isPublic: boolean; + /** + * For public threads, it must be an empty list + * + * @generated from protobuf field: repeated InputChatParticipant participants = 6; + */ + participants: InputChatParticipant[]; +} +/** + * @generated from protobuf message CreateChatResult + */ +export interface CreateChatResult { + /** + * @generated from protobuf field: Chat chat = 1; + */ + chat?: Chat; + /** + * @generated from protobuf field: Dialog dialog = 2; + */ + dialog?: Dialog; +} +/** + * @generated from protobuf message GetSpaceMembersInput + */ +export interface GetSpaceMembersInput { + /** + * @generated from protobuf field: int64 space_id = 1; + */ + spaceId: bigint; +} +/** + * @generated from protobuf message GetSpaceMembersResult + */ +export interface GetSpaceMembersResult { + /** + * @generated from protobuf field: repeated Member members = 1; + */ + members: Member[]; + /** + * @generated from protobuf field: repeated User users = 2; + */ + users: User[]; +} +/** + * / ------------------------------ + * Updates Subsystem + * + * @generated from protobuf message Update + */ +export interface Update { + /** + * @generated from protobuf field: optional int32 seq = 1; + */ + seq?: number; + /** + * @generated from protobuf field: optional int64 date = 2; + */ + date?: bigint; + /** + * @generated from protobuf oneof: update + */ + update: { + oneofKind: "newMessage"; + /** + * @generated from protobuf field: UpdateNewMessage new_message = 4; + */ + newMessage: UpdateNewMessage; // this + } | { + oneofKind: "editMessage"; + /** + * @generated from protobuf field: UpdateEditMessage edit_message = 5; + */ + editMessage: UpdateEditMessage; // this + } | { + oneofKind: "updateMessageId"; + /** + * @generated from protobuf field: UpdateMessageId update_message_id = 6; + */ + updateMessageId: UpdateMessageId; + } | { + oneofKind: "deleteMessages"; + /** + * @generated from protobuf field: UpdateDeleteMessages delete_messages = 7; + */ + deleteMessages: UpdateDeleteMessages; // this + } | { + oneofKind: "updateComposeAction"; + /** + * @generated from protobuf field: UpdateComposeAction update_compose_action = 8; + */ + updateComposeAction: UpdateComposeAction; + } | { + oneofKind: "updateUserStatus"; + /** + * @generated from protobuf field: UpdateUserStatus update_user_status = 9; + */ + updateUserStatus: UpdateUserStatus; + } | { + oneofKind: "messageAttachment"; + /** + * @generated from protobuf field: UpdateMessageAttachment message_attachment = 10; + */ + messageAttachment: UpdateMessageAttachment; // this + } | { + oneofKind: "updateReaction"; + /** + * @generated from protobuf field: UpdateReaction update_reaction = 11; + */ + updateReaction: UpdateReaction; + } | { + oneofKind: "deleteReaction"; + /** + * @generated from protobuf field: UpdateDeleteReaction delete_reaction = 12; + */ + deleteReaction: UpdateDeleteReaction; + } | { + oneofKind: "participantAdd"; + /** + * @generated from protobuf field: UpdateChatParticipantAdd participant_add = 13; + */ + participantAdd: UpdateChatParticipantAdd; // this + } | { + oneofKind: "participantDelete"; + /** + * @generated from protobuf field: UpdateChatParticipantDelete participant_delete = 14; + */ + participantDelete: UpdateChatParticipantDelete; // this + } | { + oneofKind: "newChat"; + /** + * @generated from protobuf field: UpdateNewChat new_chat = 15; + */ + newChat: UpdateNewChat; // this + } | { + oneofKind: "deleteChat"; + /** + * @generated from protobuf field: UpdateDeleteChat delete_chat = 16; + */ + deleteChat: UpdateDeleteChat; // this + } | { + oneofKind: "spaceMemberAdd"; + /** + * @generated from protobuf field: UpdateSpaceMemberAdd space_member_add = 17; + */ + spaceMemberAdd: UpdateSpaceMemberAdd; + } | { + oneofKind: "spaceMemberDelete"; + /** + * @generated from protobuf field: UpdateSpaceMemberDelete space_member_delete = 18; + */ + spaceMemberDelete: UpdateSpaceMemberDelete; // this + } | { + oneofKind: "joinSpace"; + /** + * @generated from protobuf field: UpdateJoinSpace join_space = 19; + */ + joinSpace: UpdateJoinSpace; // this + } | { + oneofKind: "updateReadMaxId"; + /** + * @generated from protobuf field: UpdateReadMaxId update_read_max_id = 20; + */ + updateReadMaxId: UpdateReadMaxId; + } | { + oneofKind: "updateUserSettings"; + /** + * @generated from protobuf field: UpdateUserSettings update_user_settings = 21; + */ + updateUserSettings: UpdateUserSettings; + } | { + oneofKind: "newMessageNotification"; + /** + * @generated from protobuf field: UpdateNewMessageNotification new_message_notification = 22; + */ + newMessageNotification: UpdateNewMessageNotification; + } | { + oneofKind: "markAsUnread"; + /** + * @generated from protobuf field: UpdateMarkAsUnread mark_as_unread = 23; + */ + markAsUnread: UpdateMarkAsUnread; + } | { + oneofKind: "chatSkipPts"; + /** + * @generated from protobuf field: UpdateChatSkipPts chat_skip_pts = 24; + */ + chatSkipPts: UpdateChatSkipPts; + } | { + oneofKind: "chatHasNewUpdates"; + /** + * @generated from protobuf field: UpdateChatHasNewUpdates chat_has_new_updates = 25; + */ + chatHasNewUpdates: UpdateChatHasNewUpdates; + } | { + oneofKind: "spaceHasNewUpdates"; + /** + * @generated from protobuf field: UpdateSpaceHasNewUpdates space_has_new_updates = 26; + */ + spaceHasNewUpdates: UpdateSpaceHasNewUpdates; + } | { + oneofKind: "spaceMemberUpdate"; + /** + * @generated from protobuf field: UpdateSpaceMemberUpdate space_member_update = 27; + */ + spaceMemberUpdate: UpdateSpaceMemberUpdate; + } | { + oneofKind: undefined; + }; +} +/** + * @generated from protobuf message UpdateSpaceHasNewUpdates + */ +export interface UpdateSpaceHasNewUpdates { + /** + * Space ID + * + * @generated from protobuf field: int64 space_id = 1; + */ + spaceId: bigint; + /** + * Current sequence of the space + * + * @generated from protobuf field: int32 update_seq = 2; + */ + updateSeq: number; +} +/** + * Update when a chat has new updates and client should fetch them + * + * @generated from protobuf message UpdateChatHasNewUpdates + */ +export interface UpdateChatHasNewUpdates { + /** + * Chat ID + * + * @generated from protobuf field: int64 chat_id = 1; + */ + chatId: bigint; + /** + * Peer ID of the chat + * + * @generated from protobuf field: Peer peer_id = 3; + */ + peerId?: Peer; + /** + * Current PTS of the chat + * + * @generated from protobuf field: int32 update_seq = 2; + */ + updateSeq: number; +} +/** + * @generated from protobuf message UpdateChatSkipPts + */ +export interface UpdateChatSkipPts { + /** + * @generated from protobuf field: int64 chat_id = 1; + */ + chatId: bigint; +} +/** + * @generated from protobuf message UpdateNewMessageNotification + */ +export interface UpdateNewMessageNotification { + /** + * Message that triggered the notification + * + * @generated from protobuf field: Message message = 1; + */ + message?: Message; + /** + * Reason for the notification + * + * @generated from protobuf field: UpdateNewMessageNotification.Reason reason = 2; + */ + reason: UpdateNewMessageNotification_Reason; +} +/** + * @generated from protobuf enum UpdateNewMessageNotification.Reason + */ +export enum UpdateNewMessageNotification_Reason { + /** + * @generated from protobuf enum value: REASON_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + /** + * @generated from protobuf enum value: REASON_MENTION = 1; + */ + MENTION = 1, + /** + * @generated from protobuf enum value: REASON_IMPORTANT = 2; + */ + IMPORTANT = 2 +} +/** + * @generated from protobuf message UpdateUserSettings + */ +export interface UpdateUserSettings { + /** + * @generated from protobuf field: UserSettings settings = 1; + */ + settings?: UserSettings; +} +/** + * Update when a new space member is added + * + * @generated from protobuf message UpdateSpaceMemberAdd + */ +export interface UpdateSpaceMemberAdd { + /** + * @generated from protobuf field: Member member = 1; + */ + member?: Member; + /** + * @generated from protobuf field: User user = 2; + */ + user?: User; +} +/** + * Update when a space member is removed + * + * @generated from protobuf message UpdateSpaceMemberDelete + */ +export interface UpdateSpaceMemberDelete { + /** + * Space ID + * + * @generated from protobuf field: int64 space_id = 1; + */ + spaceId: bigint; + /** + * User ID + * + * @generated from protobuf field: int64 user_id = 2; + */ + userId: bigint; +} +/** + * Update when a space member's access/role changes + * + * @generated from protobuf message UpdateSpaceMemberUpdate + */ +export interface UpdateSpaceMemberUpdate { + /** + * @generated from protobuf field: Member member = 1; + */ + member?: Member; +} +/** + * Update when we joined a space + * + * @generated from protobuf message UpdateJoinSpace + */ +export interface UpdateJoinSpace { + /** + * @generated from protobuf field: Space space = 1; + */ + space?: Space; + /** + * @generated from protobuf field: Member member = 2; + */ + member?: Member; +} +/** + * Update when we read up to a certain message ID + * + * @generated from protobuf message UpdateReadMaxId + */ +export interface UpdateReadMaxId { + /** + * Peer ID + * + * @generated from protobuf field: Peer peer_id = 1; + */ + peerId?: Peer; + // Chat ID + // int64 chat_id = 2; + + /** + * Read max ID + * + * @generated from protobuf field: int64 read_max_id = 3; + */ + readMaxId: bigint; + /** + * Still unread count + * + * @generated from protobuf field: int32 unread_count = 4; + */ + unreadCount: number; +} +/** + * Update when a dialog is marked as unread + * + * @generated from protobuf message UpdateMarkAsUnread + */ +export interface UpdateMarkAsUnread { + /** + * Peer ID of the dialog that was marked as unread + * + * @generated from protobuf field: Peer peer_id = 1; + */ + peerId?: Peer; + /** + * Whether it's marked as unread (true) or not (false) + * + * @generated from protobuf field: bool unread_mark = 2; + */ + unreadMark: boolean; +} +/** + * Update when a new chat is created either in space or a private chat + * + * @generated from protobuf message UpdateNewChat + */ +export interface UpdateNewChat { + /** + * Chat + * + * @generated from protobuf field: Chat chat = 1; + */ + chat?: Chat; + // Dialog for the chat + // Dialog dialog = 2; + + /** + * If private chat + * + * @generated from protobuf field: optional User user = 3; + */ + user?: User; +} +/** + * Update when a chat is deleted + * + * @generated from protobuf message UpdateDeleteChat + */ +export interface UpdateDeleteChat { + /** + * Peer ID + * + * @generated from protobuf field: Peer peer_id = 1; + */ + peerId?: Peer; +} +/** + * Update when a new message is created + * + * @generated from protobuf message UpdateNewMessage + */ +export interface UpdateNewMessage { + /** + * @generated from protobuf field: Message message = 1; + */ + message?: Message; +} +/** + * Update when a message is edited + * + * @generated from protobuf message UpdateEditMessage + */ +export interface UpdateEditMessage { + /** + * @generated from protobuf field: Message message = 1; + */ + message?: Message; +} +/** + * Update when messages are deleted + * + * @generated from protobuf message UpdateDeleteMessages + */ +export interface UpdateDeleteMessages { + /** + * Message IDs + * + * @generated from protobuf field: repeated int64 message_ids = 1; + */ + messageIds: bigint[]; + /** + * Peer ID + * + * @generated from protobuf field: Peer peer_id = 2; + */ + peerId?: Peer; +} +/** + * Update when a message ID is updated after sending + * + * @generated from protobuf message UpdateMessageId + */ +export interface UpdateMessageId { + /** + * @generated from protobuf field: int64 message_id = 1; + */ + messageId: bigint; + /** + * @generated from protobuf field: int64 random_id = 2; + */ + randomId: bigint; +} +/** + * Update when a user starts or stops composing a message for typing, uploading + * a photo, etc + * + * @generated from protobuf message UpdateComposeAction + */ +export interface UpdateComposeAction { + /** + * User ID of the user who is composing the message + * + * @generated from protobuf field: int64 user_id = 1; + */ + userId: bigint; + /** + * Peer ID of the peer user is composing the message to + * + * @generated from protobuf field: Peer peer_id = 2; + */ + peerId?: Peer; + /** + * Action of the user (typing, etc) + * + * @generated from protobuf field: UpdateComposeAction.ComposeAction action = 3; + */ + action: UpdateComposeAction_ComposeAction; +} +/** + * @generated from protobuf enum UpdateComposeAction.ComposeAction + */ +export enum UpdateComposeAction_ComposeAction { + /** + * @generated from protobuf enum value: NONE = 0; + */ + NONE = 0, + /** + * @generated from protobuf enum value: TYPING = 1; + */ + TYPING = 1, + /** + * @generated from protobuf enum value: UPLOADING_PHOTO = 2; + */ + UPLOADING_PHOTO = 2, + /** + * @generated from protobuf enum value: UPLOADING_DOCUMENT = 3; + */ + UPLOADING_DOCUMENT = 3, + /** + * @generated from protobuf enum value: UPLOADING_VIDEO = 4; + */ + UPLOADING_VIDEO = 4 +} +/** + * @generated from protobuf message UpdateMessageAttachment + */ +export interface UpdateMessageAttachment { + /** + * @generated from protobuf field: MessageAttachment attachment = 1; + */ + attachment?: MessageAttachment; + /** + * @generated from protobuf field: int64 message_id = 2; + */ + messageId: bigint; + /** + * @generated from protobuf field: Peer peer_id = 3; + */ + peerId?: Peer; + /** + * @generated from protobuf field: int64 chat_id = 50; + */ + chatId: bigint; +} +/** + * @generated from protobuf message UpdateReaction + */ +export interface UpdateReaction { + /** + * @generated from protobuf field: Reaction reaction = 1; + */ + reaction?: Reaction; +} +/** + * @generated from protobuf message UpdateDeleteReaction + */ +export interface UpdateDeleteReaction { + /** + * @generated from protobuf field: string emoji = 1; + */ + emoji: string; + /** + * @generated from protobuf field: int64 chat_id = 2; + */ + chatId: bigint; + /** + * @generated from protobuf field: int64 message_id = 3; + */ + messageId: bigint; + /** + * @generated from protobuf field: int64 user_id = 4; + */ + userId: bigint; +} +/** + * @generated from protobuf message UpdateUserStatus + */ +export interface UpdateUserStatus { + /** + * @generated from protobuf field: int64 user_id = 1; + */ + userId: bigint; + /** + * @generated from protobuf field: UserStatus status = 2; + */ + status?: UserStatus; +} +/** + * @generated from protobuf message ChatParticipant + */ +export interface ChatParticipant { + /** + * @generated from protobuf field: int64 user_id = 1; + */ + userId: bigint; + /** + * @generated from protobuf field: int64 date = 2; + */ + date: bigint; +} +/** + * @generated from protobuf message UpdateChatParticipantAdd + */ +export interface UpdateChatParticipantAdd { + /** + * @generated from protobuf field: int64 chat_id = 1; + */ + chatId: bigint; + /** + * @generated from protobuf field: ChatParticipant participant = 2; + */ + participant?: ChatParticipant; +} +/** + * @generated from protobuf message UpdateChatParticipantDelete + */ +export interface UpdateChatParticipantDelete { + /** + * @generated from protobuf field: int64 chat_id = 1; + */ + chatId: bigint; + /** + * @generated from protobuf field: int64 user_id = 2; + */ + userId: bigint; +} +/** + * @generated from protobuf message UserStatus + */ +export interface UserStatus { + /** + * @generated from protobuf field: UserStatus.Status online = 1; + */ + online: UserStatus_Status; + /** + * @generated from protobuf field: LastOnline last_online = 2; + */ + lastOnline?: LastOnline; +} +/** + * @generated from protobuf enum UserStatus.Status + */ +export enum UserStatus_Status { + /** + * @generated from protobuf enum value: UNKNOWN = 0; + */ + UNKNOWN = 0, + /** + * @generated from protobuf enum value: ONLINE = 1; + */ + ONLINE = 1, + /** + * @generated from protobuf enum value: OFFLINE = 2; + */ + OFFLINE = 2 +} +/** + * @generated from protobuf message LastOnline + */ +export interface LastOnline { + /** + * Date of the last online if exact last online is permitted by the user + * + * @generated from protobuf field: optional int64 date = 1; + */ + date?: bigint; +} +/** + * @generated from protobuf message DeleteChatInput + */ +export interface DeleteChatInput { + /** + * @generated from protobuf field: InputPeer peer_id = 1; + */ + peerId?: InputPeer; +} +/** + * @generated from protobuf message DeleteChatResult + */ +export interface DeleteChatResult { +} +/** + * @generated from protobuf message SpaceMemberOptions + */ +export interface SpaceMemberOptions { + /** + * @generated from protobuf field: bool can_access_public_chats = 1; + */ + canAccessPublicChats: boolean; +} +/** + * @generated from protobuf message SpaceAdminOptions + */ +export interface SpaceAdminOptions { +} +/** + * @generated from protobuf message SpaceMemberRole + */ +export interface SpaceMemberRole { + /** + * @generated from protobuf oneof: role + */ + role: { + oneofKind: "member"; + /** + * @generated from protobuf field: SpaceMemberOptions member = 1; + */ + member: SpaceMemberOptions; + } | { + oneofKind: "admin"; + /** + * @generated from protobuf field: SpaceAdminOptions admin = 2; + */ + admin: SpaceAdminOptions; + } | { + oneofKind: undefined; + }; +} +/** + * @generated from protobuf message InviteToSpaceInput + */ +export interface InviteToSpaceInput { + /** + * ID of the space to invite to + * + * @generated from protobuf field: int64 space_id = 1; + */ + spaceId: bigint; + /** + * @generated from protobuf field: SpaceMemberRole role = 6; + */ + role?: SpaceMemberRole; + /** + * @generated from protobuf oneof: via + */ + via: { + oneofKind: "userId"; + /** + * ID of the user to invite + * + * @generated from protobuf field: int64 user_id = 3; + */ + userId: bigint; + } | { + oneofKind: "email"; + /** + * Email of the user to invite + * + * @generated from protobuf field: string email = 4; + */ + email: string; + } | { + oneofKind: "phoneNumber"; + /** + * Phone number of the user to invite + * + * @generated from protobuf field: string phone_number = 5; + */ + phoneNumber: string; + } | { + oneofKind: undefined; + }; +} +/** + * @generated from protobuf message InviteToSpaceResult + */ +export interface InviteToSpaceResult { + /** + * @generated from protobuf field: User user = 1; + */ + user?: User; + /** + * @generated from protobuf field: Member member = 2; + */ + member?: Member; + /** + * @generated from protobuf field: Chat chat = 3; + */ + chat?: Chat; + /** + * @generated from protobuf field: Dialog dialog = 4; + */ + dialog?: Dialog; +} +/** + * @generated from protobuf message GetChatParticipantsInput + */ +export interface GetChatParticipantsInput { + /** + * @generated from protobuf field: int64 chat_id = 1; + */ + chatId: bigint; +} +/** + * @generated from protobuf message GetChatParticipantsResult + */ +export interface GetChatParticipantsResult { + /** + * @generated from protobuf field: repeated ChatParticipant participants = 1; + */ + participants: ChatParticipant[]; + /** + * @generated from protobuf field: repeated User users = 2; + */ + users: User[]; +} +/** + * @generated from protobuf message AddChatParticipantInput + */ +export interface AddChatParticipantInput { + /** + * @generated from protobuf field: int64 chat_id = 1; + */ + chatId: bigint; + /** + * @generated from protobuf field: int64 user_id = 2; + */ + userId: bigint; +} +/** + * @generated from protobuf message AddChatParticipantResult + */ +export interface AddChatParticipantResult { + /** + * @generated from protobuf field: ChatParticipant participant = 1; + */ + participant?: ChatParticipant; +} +/** + * @generated from protobuf message RemoveChatParticipantInput + */ +export interface RemoveChatParticipantInput { + /** + * @generated from protobuf field: int64 chat_id = 1; + */ + chatId: bigint; + /** + * @generated from protobuf field: int64 user_id = 2; + */ + userId: bigint; +} +/** + * @generated from protobuf message RemoveChatParticipantResult + */ +export interface RemoveChatParticipantResult { +} +/** + * Apple only types + * + * @generated from protobuf message DraftMessage + */ +export interface DraftMessage { + /** + * @generated from protobuf field: string text = 1; + */ + text: string; + /** + * @generated from protobuf field: optional MessageEntities entities = 2; + */ + entities?: MessageEntities; +} +/** + * @generated from protobuf enum Method + */ +export enum Method { + /** + * @generated from protobuf enum value: UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + /** + * @generated from protobuf enum value: GET_ME = 1; + */ + GET_ME = 1, + /** + * @generated from protobuf enum value: SEND_MESSAGE = 2; + */ + SEND_MESSAGE = 2, + /** + * @generated from protobuf enum value: GET_PEER_PHOTO = 3; + */ + GET_PEER_PHOTO = 3, + /** + * @generated from protobuf enum value: DELETE_MESSAGES = 4; + */ + DELETE_MESSAGES = 4, + /** + * @generated from protobuf enum value: GET_CHAT_HISTORY = 5; + */ + GET_CHAT_HISTORY = 5, + /** + * @generated from protobuf enum value: ADD_REACTION = 6; + */ + ADD_REACTION = 6, + /** + * @generated from protobuf enum value: DELETE_REACTION = 7; + */ + DELETE_REACTION = 7, + /** + * @generated from protobuf enum value: EDIT_MESSAGE = 8; + */ + EDIT_MESSAGE = 8, + /** + * @generated from protobuf enum value: CREATE_CHAT = 9; + */ + CREATE_CHAT = 9, + /** + * @generated from protobuf enum value: GET_SPACE_MEMBERS = 10; + */ + GET_SPACE_MEMBERS = 10, + /** + * @generated from protobuf enum value: DELETE_CHAT = 11; + */ + DELETE_CHAT = 11, + /** + * @generated from protobuf enum value: INVITE_TO_SPACE = 12; + */ + INVITE_TO_SPACE = 12, + /** + * @generated from protobuf enum value: GET_CHAT_PARTICIPANTS = 13; + */ + GET_CHAT_PARTICIPANTS = 13, + /** + * @generated from protobuf enum value: ADD_CHAT_PARTICIPANT = 14; + */ + ADD_CHAT_PARTICIPANT = 14, + /** + * @generated from protobuf enum value: REMOVE_CHAT_PARTICIPANT = 15; + */ + REMOVE_CHAT_PARTICIPANT = 15, + /** + * @generated from protobuf enum value: TRANSLATE_MESSAGES = 16; + */ + TRANSLATE_MESSAGES = 16, + /** + * @generated from protobuf enum value: GET_CHATS = 17; + */ + GET_CHATS = 17, + /** + * @generated from protobuf enum value: UPDATE_USER_SETTINGS = 18; + */ + UPDATE_USER_SETTINGS = 18, + /** + * @generated from protobuf enum value: GET_USER_SETTINGS = 19; + */ + GET_USER_SETTINGS = 19, + /** + * @generated from protobuf enum value: SEND_COMPOSE_ACTION = 20; + */ + SEND_COMPOSE_ACTION = 20, + /** + * @generated from protobuf enum value: CREATE_BOT = 21; + */ + CREATE_BOT = 21, + /** + * @generated from protobuf enum value: DELETE_MEMBER = 22; + */ + DELETE_MEMBER = 22, + /** + * @generated from protobuf enum value: MARK_AS_UNREAD = 23; + */ + MARK_AS_UNREAD = 23, + /** + * @generated from protobuf enum value: GET_UPDATES_STATE = 24; + */ + GET_UPDATES_STATE = 24, + /** + * @generated from protobuf enum value: GET_CHAT = 25; + */ + GET_CHAT = 25, + /** + * @generated from protobuf enum value: GET_UPDATES = 26; + */ + GET_UPDATES = 26, + /** + * @generated from protobuf enum value: UPDATE_MEMBER_ACCESS = 27; + */ + UPDATE_MEMBER_ACCESS = 27 +} +// @generated message type with reflection information, may provide speed optimized methods +class ClientMessage$Type extends MessageType { + constructor() { + super("ClientMessage", [ + { no: 1, name: "id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "seq", kind: "scalar", T: 13 /*ScalarType.UINT32*/ }, + { no: 4, name: "connection_init", kind: "message", oneof: "body", T: () => ConnectionInit }, + { no: 5, name: "rpc_call", kind: "message", oneof: "body", T: () => RpcCall }, + { no: 6, name: "ack", kind: "message", oneof: "body", T: () => Ack }, + { no: 7, name: "ping", kind: "message", oneof: "body", T: () => Ping } + ]); + } + create(value?: PartialMessage): ClientMessage { + const message = globalThis.Object.create((this.messagePrototype!)); + message.id = 0n; + message.seq = 0; + message.body = { oneofKind: undefined }; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ClientMessage): ClientMessage { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* uint64 id */ 1: + message.id = reader.uint64().toBigInt(); + break; + case /* uint32 seq */ 2: + message.seq = reader.uint32(); + break; + case /* ConnectionInit connection_init */ 4: + message.body = { + oneofKind: "connectionInit", + connectionInit: ConnectionInit.internalBinaryRead(reader, reader.uint32(), options, (message.body as any).connectionInit) + }; + break; + case /* RpcCall rpc_call */ 5: + message.body = { + oneofKind: "rpcCall", + rpcCall: RpcCall.internalBinaryRead(reader, reader.uint32(), options, (message.body as any).rpcCall) + }; + break; + case /* Ack ack */ 6: + message.body = { + oneofKind: "ack", + ack: Ack.internalBinaryRead(reader, reader.uint32(), options, (message.body as any).ack) + }; + break; + case /* Ping ping */ 7: + message.body = { + oneofKind: "ping", + ping: Ping.internalBinaryRead(reader, reader.uint32(), options, (message.body as any).ping) + }; + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: ClientMessage, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* uint64 id = 1; */ + if (message.id !== 0n) + writer.tag(1, WireType.Varint).uint64(message.id); + /* uint32 seq = 2; */ + if (message.seq !== 0) + writer.tag(2, WireType.Varint).uint32(message.seq); + /* ConnectionInit connection_init = 4; */ + if (message.body.oneofKind === "connectionInit") + ConnectionInit.internalBinaryWrite(message.body.connectionInit, writer.tag(4, WireType.LengthDelimited).fork(), options).join(); + /* RpcCall rpc_call = 5; */ + if (message.body.oneofKind === "rpcCall") + RpcCall.internalBinaryWrite(message.body.rpcCall, writer.tag(5, WireType.LengthDelimited).fork(), options).join(); + /* Ack ack = 6; */ + if (message.body.oneofKind === "ack") + Ack.internalBinaryWrite(message.body.ack, writer.tag(6, WireType.LengthDelimited).fork(), options).join(); + /* Ping ping = 7; */ + if (message.body.oneofKind === "ping") + Ping.internalBinaryWrite(message.body.ping, writer.tag(7, WireType.LengthDelimited).fork(), options).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message ClientMessage + */ +export const ClientMessage = new ClientMessage$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class ConnectionInit$Type extends MessageType { + constructor() { + super("ConnectionInit", [ + { no: 1, name: "token", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 2, name: "build_number", kind: "scalar", opt: true, T: 5 /*ScalarType.INT32*/ }, + { no: 3, name: "layer", kind: "scalar", opt: true, T: 13 /*ScalarType.UINT32*/ } + ]); + } + create(value?: PartialMessage): ConnectionInit { + const message = globalThis.Object.create((this.messagePrototype!)); + message.token = ""; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ConnectionInit): ConnectionInit { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* string token */ 1: + message.token = reader.string(); + break; + case /* optional int32 build_number */ 2: + message.buildNumber = reader.int32(); + break; + case /* optional uint32 layer */ 3: + message.layer = reader.uint32(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: ConnectionInit, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* string token = 1; */ + if (message.token !== "") + writer.tag(1, WireType.LengthDelimited).string(message.token); + /* optional int32 build_number = 2; */ + if (message.buildNumber !== undefined) + writer.tag(2, WireType.Varint).int32(message.buildNumber); + /* optional uint32 layer = 3; */ + if (message.layer !== undefined) + writer.tag(3, WireType.Varint).uint32(message.layer); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message ConnectionInit + */ +export const ConnectionInit = new ConnectionInit$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class ServerProtocolMessage$Type extends MessageType { + constructor() { + super("ServerProtocolMessage", [ + { no: 1, name: "id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 4, name: "connection_open", kind: "message", oneof: "body", T: () => ConnectionOpen }, + { no: 5, name: "rpc_result", kind: "message", oneof: "body", T: () => RpcResult }, + { no: 6, name: "rpc_error", kind: "message", oneof: "body", T: () => RpcError }, + { no: 7, name: "message", kind: "message", oneof: "body", T: () => ServerMessage }, + { no: 8, name: "ack", kind: "message", oneof: "body", T: () => Ack }, + { no: 9, name: "pong", kind: "message", oneof: "body", T: () => Pong }, + { no: 10, name: "connection_error", kind: "message", oneof: "body", T: () => ConnectionError } + ]); + } + create(value?: PartialMessage): ServerProtocolMessage { + const message = globalThis.Object.create((this.messagePrototype!)); + message.id = 0n; + message.body = { oneofKind: undefined }; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ServerProtocolMessage): ServerProtocolMessage { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* uint64 id */ 1: + message.id = reader.uint64().toBigInt(); + break; + case /* ConnectionOpen connection_open */ 4: + message.body = { + oneofKind: "connectionOpen", + connectionOpen: ConnectionOpen.internalBinaryRead(reader, reader.uint32(), options, (message.body as any).connectionOpen) + }; + break; + case /* RpcResult rpc_result */ 5: + message.body = { + oneofKind: "rpcResult", + rpcResult: RpcResult.internalBinaryRead(reader, reader.uint32(), options, (message.body as any).rpcResult) + }; + break; + case /* RpcError rpc_error */ 6: + message.body = { + oneofKind: "rpcError", + rpcError: RpcError.internalBinaryRead(reader, reader.uint32(), options, (message.body as any).rpcError) + }; + break; + case /* ServerMessage message */ 7: + message.body = { + oneofKind: "message", + message: ServerMessage.internalBinaryRead(reader, reader.uint32(), options, (message.body as any).message) + }; + break; + case /* Ack ack */ 8: + message.body = { + oneofKind: "ack", + ack: Ack.internalBinaryRead(reader, reader.uint32(), options, (message.body as any).ack) + }; + break; + case /* Pong pong */ 9: + message.body = { + oneofKind: "pong", + pong: Pong.internalBinaryRead(reader, reader.uint32(), options, (message.body as any).pong) + }; + break; + case /* ConnectionError connection_error */ 10: + message.body = { + oneofKind: "connectionError", + connectionError: ConnectionError.internalBinaryRead(reader, reader.uint32(), options, (message.body as any).connectionError) + }; + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: ServerProtocolMessage, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* uint64 id = 1; */ + if (message.id !== 0n) + writer.tag(1, WireType.Varint).uint64(message.id); + /* ConnectionOpen connection_open = 4; */ + if (message.body.oneofKind === "connectionOpen") + ConnectionOpen.internalBinaryWrite(message.body.connectionOpen, writer.tag(4, WireType.LengthDelimited).fork(), options).join(); + /* RpcResult rpc_result = 5; */ + if (message.body.oneofKind === "rpcResult") + RpcResult.internalBinaryWrite(message.body.rpcResult, writer.tag(5, WireType.LengthDelimited).fork(), options).join(); + /* RpcError rpc_error = 6; */ + if (message.body.oneofKind === "rpcError") + RpcError.internalBinaryWrite(message.body.rpcError, writer.tag(6, WireType.LengthDelimited).fork(), options).join(); + /* ServerMessage message = 7; */ + if (message.body.oneofKind === "message") + ServerMessage.internalBinaryWrite(message.body.message, writer.tag(7, WireType.LengthDelimited).fork(), options).join(); + /* Ack ack = 8; */ + if (message.body.oneofKind === "ack") + Ack.internalBinaryWrite(message.body.ack, writer.tag(8, WireType.LengthDelimited).fork(), options).join(); + /* Pong pong = 9; */ + if (message.body.oneofKind === "pong") + Pong.internalBinaryWrite(message.body.pong, writer.tag(9, WireType.LengthDelimited).fork(), options).join(); + /* ConnectionError connection_error = 10; */ + if (message.body.oneofKind === "connectionError") + ConnectionError.internalBinaryWrite(message.body.connectionError, writer.tag(10, WireType.LengthDelimited).fork(), options).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message ServerProtocolMessage + */ +export const ServerProtocolMessage = new ServerProtocolMessage$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class ServerMessage$Type extends MessageType { + constructor() { + super("ServerMessage", [ + { no: 4, name: "update", kind: "message", oneof: "payload", T: () => UpdatesPayload } + ]); + } + create(value?: PartialMessage): ServerMessage { + const message = globalThis.Object.create((this.messagePrototype!)); + message.payload = { oneofKind: undefined }; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ServerMessage): ServerMessage { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* UpdatesPayload update */ 4: + message.payload = { + oneofKind: "update", + update: UpdatesPayload.internalBinaryRead(reader, reader.uint32(), options, (message.payload as any).update) + }; + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: ServerMessage, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* UpdatesPayload update = 4; */ + if (message.payload.oneofKind === "update") + UpdatesPayload.internalBinaryWrite(message.payload.update, writer.tag(4, WireType.LengthDelimited).fork(), options).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message ServerMessage + */ +export const ServerMessage = new ServerMessage$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class UpdatesPayload$Type extends MessageType { + constructor() { + super("UpdatesPayload", [ + { no: 1, name: "updates", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => Update } + ]); + } + create(value?: PartialMessage): UpdatesPayload { + const message = globalThis.Object.create((this.messagePrototype!)); + message.updates = []; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: UpdatesPayload): UpdatesPayload { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* repeated Update updates */ 1: + message.updates.push(Update.internalBinaryRead(reader, reader.uint32(), options)); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: UpdatesPayload, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* repeated Update updates = 1; */ + for (let i = 0; i < message.updates.length; i++) + Update.internalBinaryWrite(message.updates[i], writer.tag(1, WireType.LengthDelimited).fork(), options).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message UpdatesPayload + */ +export const UpdatesPayload = new UpdatesPayload$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class Ack$Type extends MessageType { + constructor() { + super("Ack", [ + { no: 1, name: "msg_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ } + ]); + } + create(value?: PartialMessage): Ack { + const message = globalThis.Object.create((this.messagePrototype!)); + message.msgId = 0n; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Ack): Ack { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* uint64 msg_id */ 1: + message.msgId = reader.uint64().toBigInt(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: Ack, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* uint64 msg_id = 1; */ + if (message.msgId !== 0n) + writer.tag(1, WireType.Varint).uint64(message.msgId); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message Ack + */ +export const Ack = new Ack$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class ConnectionOpen$Type extends MessageType { + constructor() { + super("ConnectionOpen", []); + } + create(value?: PartialMessage): ConnectionOpen { + const message = globalThis.Object.create((this.messagePrototype!)); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ConnectionOpen): ConnectionOpen { + return target ?? this.create(); + } + internalBinaryWrite(message: ConnectionOpen, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message ConnectionOpen + */ +export const ConnectionOpen = new ConnectionOpen$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class ConnectionError$Type extends MessageType { + constructor() { + super("ConnectionError", []); + } + create(value?: PartialMessage): ConnectionError { + const message = globalThis.Object.create((this.messagePrototype!)); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ConnectionError): ConnectionError { + return target ?? this.create(); + } + internalBinaryWrite(message: ConnectionError, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message ConnectionError + */ +export const ConnectionError = new ConnectionError$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class Ping$Type extends MessageType { + constructor() { + super("Ping", [ + { no: 1, name: "nonce", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ } + ]); + } + create(value?: PartialMessage): Ping { + const message = globalThis.Object.create((this.messagePrototype!)); + message.nonce = 0n; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Ping): Ping { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* uint64 nonce */ 1: + message.nonce = reader.uint64().toBigInt(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: Ping, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* uint64 nonce = 1; */ + if (message.nonce !== 0n) + writer.tag(1, WireType.Varint).uint64(message.nonce); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message Ping + */ +export const Ping = new Ping$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class Pong$Type extends MessageType { + constructor() { + super("Pong", [ + { no: 1, name: "nonce", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ } + ]); + } + create(value?: PartialMessage): Pong { + const message = globalThis.Object.create((this.messagePrototype!)); + message.nonce = 0n; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Pong): Pong { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* uint64 nonce */ 1: + message.nonce = reader.uint64().toBigInt(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: Pong, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* uint64 nonce = 1; */ + if (message.nonce !== 0n) + writer.tag(1, WireType.Varint).uint64(message.nonce); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message Pong + */ +export const Pong = new Pong$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class InputPeer$Type extends MessageType { + constructor() { + super("InputPeer", [ + { no: 2, name: "self", kind: "message", oneof: "type", T: () => InputPeerSelf }, + { no: 3, name: "chat", kind: "message", oneof: "type", T: () => InputPeerChat }, + { no: 4, name: "user", kind: "message", oneof: "type", T: () => InputPeerUser } + ]); + } + create(value?: PartialMessage): InputPeer { + const message = globalThis.Object.create((this.messagePrototype!)); + message.type = { oneofKind: undefined }; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: InputPeer): InputPeer { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* InputPeerSelf self */ 2: + message.type = { + oneofKind: "self", + self: InputPeerSelf.internalBinaryRead(reader, reader.uint32(), options, (message.type as any).self) + }; + break; + case /* InputPeerChat chat */ 3: + message.type = { + oneofKind: "chat", + chat: InputPeerChat.internalBinaryRead(reader, reader.uint32(), options, (message.type as any).chat) + }; + break; + case /* InputPeerUser user */ 4: + message.type = { + oneofKind: "user", + user: InputPeerUser.internalBinaryRead(reader, reader.uint32(), options, (message.type as any).user) + }; + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: InputPeer, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* InputPeerSelf self = 2; */ + if (message.type.oneofKind === "self") + InputPeerSelf.internalBinaryWrite(message.type.self, writer.tag(2, WireType.LengthDelimited).fork(), options).join(); + /* InputPeerChat chat = 3; */ + if (message.type.oneofKind === "chat") + InputPeerChat.internalBinaryWrite(message.type.chat, writer.tag(3, WireType.LengthDelimited).fork(), options).join(); + /* InputPeerUser user = 4; */ + if (message.type.oneofKind === "user") + InputPeerUser.internalBinaryWrite(message.type.user, writer.tag(4, WireType.LengthDelimited).fork(), options).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message InputPeer + */ +export const InputPeer = new InputPeer$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class InputPeerSelf$Type extends MessageType { + constructor() { + super("InputPeerSelf", []); + } + create(value?: PartialMessage): InputPeerSelf { + const message = globalThis.Object.create((this.messagePrototype!)); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: InputPeerSelf): InputPeerSelf { + return target ?? this.create(); + } + internalBinaryWrite(message: InputPeerSelf, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message InputPeerSelf + */ +export const InputPeerSelf = new InputPeerSelf$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class InputPeerChat$Type extends MessageType { + constructor() { + super("InputPeerChat", [ + { no: 1, name: "chat_id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ } + ]); + } + create(value?: PartialMessage): InputPeerChat { + const message = globalThis.Object.create((this.messagePrototype!)); + message.chatId = 0n; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: InputPeerChat): InputPeerChat { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* int64 chat_id */ 1: + message.chatId = reader.int64().toBigInt(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: InputPeerChat, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* int64 chat_id = 1; */ + if (message.chatId !== 0n) + writer.tag(1, WireType.Varint).int64(message.chatId); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message InputPeerChat + */ +export const InputPeerChat = new InputPeerChat$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class InputPeerUser$Type extends MessageType { + constructor() { + super("InputPeerUser", [ + { no: 1, name: "user_id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ } + ]); + } + create(value?: PartialMessage): InputPeerUser { + const message = globalThis.Object.create((this.messagePrototype!)); + message.userId = 0n; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: InputPeerUser): InputPeerUser { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* int64 user_id */ 1: + message.userId = reader.int64().toBigInt(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: InputPeerUser, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* int64 user_id = 1; */ + if (message.userId !== 0n) + writer.tag(1, WireType.Varint).int64(message.userId); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message InputPeerUser + */ +export const InputPeerUser = new InputPeerUser$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class Peer$Type extends MessageType { + constructor() { + super("Peer", [ + { no: 2, name: "chat", kind: "message", oneof: "type", T: () => PeerChat }, + { no: 3, name: "user", kind: "message", oneof: "type", T: () => PeerUser } + ]); + } + create(value?: PartialMessage): Peer { + const message = globalThis.Object.create((this.messagePrototype!)); + message.type = { oneofKind: undefined }; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Peer): Peer { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* PeerChat chat */ 2: + message.type = { + oneofKind: "chat", + chat: PeerChat.internalBinaryRead(reader, reader.uint32(), options, (message.type as any).chat) + }; + break; + case /* PeerUser user */ 3: + message.type = { + oneofKind: "user", + user: PeerUser.internalBinaryRead(reader, reader.uint32(), options, (message.type as any).user) + }; + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: Peer, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* PeerChat chat = 2; */ + if (message.type.oneofKind === "chat") + PeerChat.internalBinaryWrite(message.type.chat, writer.tag(2, WireType.LengthDelimited).fork(), options).join(); + /* PeerUser user = 3; */ + if (message.type.oneofKind === "user") + PeerUser.internalBinaryWrite(message.type.user, writer.tag(3, WireType.LengthDelimited).fork(), options).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message Peer + */ +export const Peer = new Peer$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class PeerChat$Type extends MessageType { + constructor() { + super("PeerChat", [ + { no: 1, name: "chat_id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ } + ]); + } + create(value?: PartialMessage): PeerChat { + const message = globalThis.Object.create((this.messagePrototype!)); + message.chatId = 0n; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: PeerChat): PeerChat { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* int64 chat_id */ 1: + message.chatId = reader.int64().toBigInt(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: PeerChat, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* int64 chat_id = 1; */ + if (message.chatId !== 0n) + writer.tag(1, WireType.Varint).int64(message.chatId); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message PeerChat + */ +export const PeerChat = new PeerChat$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class PeerUser$Type extends MessageType { + constructor() { + super("PeerUser", [ + { no: 1, name: "user_id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ } + ]); + } + create(value?: PartialMessage): PeerUser { + const message = globalThis.Object.create((this.messagePrototype!)); + message.userId = 0n; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: PeerUser): PeerUser { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* int64 user_id */ 1: + message.userId = reader.int64().toBigInt(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: PeerUser, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* int64 user_id = 1; */ + if (message.userId !== 0n) + writer.tag(1, WireType.Varint).int64(message.userId); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message PeerUser + */ +export const PeerUser = new PeerUser$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class User$Type extends MessageType { + constructor() { + super("User", [ + { no: 1, name: "id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "first_name", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, + { no: 3, name: "last_name", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, + { no: 4, name: "username", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, + { no: 5, name: "phone_number", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, + { no: 6, name: "email", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, + { no: 7, name: "min", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, + { no: 8, name: "status", kind: "message", T: () => UserStatus }, + { no: 9, name: "profile_photo", kind: "message", T: () => UserProfilePhoto }, + { no: 11, name: "pending_setup", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, + { no: 12, name: "time_zone", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, + { no: 13, name: "bot", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ } + ]); + } + create(value?: PartialMessage): User { + const message = globalThis.Object.create((this.messagePrototype!)); + message.id = 0n; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: User): User { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* int64 id */ 1: + message.id = reader.int64().toBigInt(); + break; + case /* optional string first_name */ 2: + message.firstName = reader.string(); + break; + case /* optional string last_name */ 3: + message.lastName = reader.string(); + break; + case /* optional string username */ 4: + message.username = reader.string(); + break; + case /* optional string phone_number */ 5: + message.phoneNumber = reader.string(); + break; + case /* optional string email */ 6: + message.email = reader.string(); + break; + case /* optional bool min */ 7: + message.min = reader.bool(); + break; + case /* optional UserStatus status */ 8: + message.status = UserStatus.internalBinaryRead(reader, reader.uint32(), options, message.status); + break; + case /* optional UserProfilePhoto profile_photo */ 9: + message.profilePhoto = UserProfilePhoto.internalBinaryRead(reader, reader.uint32(), options, message.profilePhoto); + break; + case /* optional bool pending_setup */ 11: + message.pendingSetup = reader.bool(); + break; + case /* optional string time_zone */ 12: + message.timeZone = reader.string(); + break; + case /* optional bool bot */ 13: + message.bot = reader.bool(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: User, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* int64 id = 1; */ + if (message.id !== 0n) + writer.tag(1, WireType.Varint).int64(message.id); + /* optional string first_name = 2; */ + if (message.firstName !== undefined) + writer.tag(2, WireType.LengthDelimited).string(message.firstName); + /* optional string last_name = 3; */ + if (message.lastName !== undefined) + writer.tag(3, WireType.LengthDelimited).string(message.lastName); + /* optional string username = 4; */ + if (message.username !== undefined) + writer.tag(4, WireType.LengthDelimited).string(message.username); + /* optional string phone_number = 5; */ + if (message.phoneNumber !== undefined) + writer.tag(5, WireType.LengthDelimited).string(message.phoneNumber); + /* optional string email = 6; */ + if (message.email !== undefined) + writer.tag(6, WireType.LengthDelimited).string(message.email); + /* optional bool min = 7; */ + if (message.min !== undefined) + writer.tag(7, WireType.Varint).bool(message.min); + /* optional UserStatus status = 8; */ + if (message.status) + UserStatus.internalBinaryWrite(message.status, writer.tag(8, WireType.LengthDelimited).fork(), options).join(); + /* optional UserProfilePhoto profile_photo = 9; */ + if (message.profilePhoto) + UserProfilePhoto.internalBinaryWrite(message.profilePhoto, writer.tag(9, WireType.LengthDelimited).fork(), options).join(); + /* optional bool pending_setup = 11; */ + if (message.pendingSetup !== undefined) + writer.tag(11, WireType.Varint).bool(message.pendingSetup); + /* optional string time_zone = 12; */ + if (message.timeZone !== undefined) + writer.tag(12, WireType.LengthDelimited).string(message.timeZone); + /* optional bool bot = 13; */ + if (message.bot !== undefined) + writer.tag(13, WireType.Varint).bool(message.bot); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message User + */ +export const User = new User$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class UserProfilePhoto$Type extends MessageType { + constructor() { + super("UserProfilePhoto", [ + { no: 1, name: "photo_id", kind: "scalar", opt: true, T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "stripped_thumb", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ }, + { no: 3, name: "cdn_url", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, + { no: 4, name: "file_unique_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ } + ]); + } + create(value?: PartialMessage): UserProfilePhoto { + const message = globalThis.Object.create((this.messagePrototype!)); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: UserProfilePhoto): UserProfilePhoto { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* optional int64 photo_id */ 1: + message.photoId = reader.int64().toBigInt(); + break; + case /* optional bytes stripped_thumb */ 2: + message.strippedThumb = reader.bytes(); + break; + case /* optional string cdn_url */ 3: + message.cdnUrl = reader.string(); + break; + case /* optional string file_unique_id */ 4: + message.fileUniqueId = reader.string(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: UserProfilePhoto, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* optional int64 photo_id = 1; */ + if (message.photoId !== undefined) + writer.tag(1, WireType.Varint).int64(message.photoId); + /* optional bytes stripped_thumb = 2; */ + if (message.strippedThumb !== undefined) + writer.tag(2, WireType.LengthDelimited).bytes(message.strippedThumb); + /* optional string cdn_url = 3; */ + if (message.cdnUrl !== undefined) + writer.tag(3, WireType.LengthDelimited).string(message.cdnUrl); + /* optional string file_unique_id = 4; */ + if (message.fileUniqueId !== undefined) + writer.tag(4, WireType.LengthDelimited).string(message.fileUniqueId); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message UserProfilePhoto + */ +export const UserProfilePhoto = new UserProfilePhoto$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class Dialog$Type extends MessageType { + constructor() { + super("Dialog", [ + { no: 1, name: "peer", kind: "message", T: () => Peer }, + { no: 2, name: "space_id", kind: "scalar", opt: true, T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 3, name: "archived", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, + { no: 4, name: "pinned", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, + { no: 5, name: "read_max_id", kind: "scalar", opt: true, T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 6, name: "unread_count", kind: "scalar", opt: true, T: 5 /*ScalarType.INT32*/ }, + { no: 7, name: "chat_id", kind: "scalar", opt: true, T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 8, name: "unread_mark", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ } + ]); + } + create(value?: PartialMessage): Dialog { + const message = globalThis.Object.create((this.messagePrototype!)); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Dialog): Dialog { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* Peer peer */ 1: + message.peer = Peer.internalBinaryRead(reader, reader.uint32(), options, message.peer); + break; + case /* optional int64 space_id */ 2: + message.spaceId = reader.int64().toBigInt(); + break; + case /* optional bool archived */ 3: + message.archived = reader.bool(); + break; + case /* optional bool pinned */ 4: + message.pinned = reader.bool(); + break; + case /* optional int64 read_max_id */ 5: + message.readMaxId = reader.int64().toBigInt(); + break; + case /* optional int32 unread_count */ 6: + message.unreadCount = reader.int32(); + break; + case /* optional int64 chat_id */ 7: + message.chatId = reader.int64().toBigInt(); + break; + case /* optional bool unread_mark */ 8: + message.unreadMark = reader.bool(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: Dialog, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* Peer peer = 1; */ + if (message.peer) + Peer.internalBinaryWrite(message.peer, writer.tag(1, WireType.LengthDelimited).fork(), options).join(); + /* optional int64 space_id = 2; */ + if (message.spaceId !== undefined) + writer.tag(2, WireType.Varint).int64(message.spaceId); + /* optional bool archived = 3; */ + if (message.archived !== undefined) + writer.tag(3, WireType.Varint).bool(message.archived); + /* optional bool pinned = 4; */ + if (message.pinned !== undefined) + writer.tag(4, WireType.Varint).bool(message.pinned); + /* optional int64 read_max_id = 5; */ + if (message.readMaxId !== undefined) + writer.tag(5, WireType.Varint).int64(message.readMaxId); + /* optional int32 unread_count = 6; */ + if (message.unreadCount !== undefined) + writer.tag(6, WireType.Varint).int32(message.unreadCount); + /* optional int64 chat_id = 7; */ + if (message.chatId !== undefined) + writer.tag(7, WireType.Varint).int64(message.chatId); + /* optional bool unread_mark = 8; */ + if (message.unreadMark !== undefined) + writer.tag(8, WireType.Varint).bool(message.unreadMark); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message Dialog + */ +export const Dialog = new Dialog$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class Chat$Type extends MessageType { + constructor() { + super("Chat", [ + { no: 1, name: "id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "title", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 3, name: "space_id", kind: "scalar", opt: true, T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 4, name: "description", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, + { no: 5, name: "emoji", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, + { no: 6, name: "is_public", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, + { no: 7, name: "last_msg_id", kind: "scalar", opt: true, T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 8, name: "peer_id", kind: "message", T: () => Peer }, + { no: 9, name: "date", kind: "scalar", opt: true, T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ } + ]); + } + create(value?: PartialMessage): Chat { + const message = globalThis.Object.create((this.messagePrototype!)); + message.id = 0n; + message.title = ""; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Chat): Chat { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* int64 id */ 1: + message.id = reader.int64().toBigInt(); + break; + case /* string title */ 2: + message.title = reader.string(); + break; + case /* optional int64 space_id */ 3: + message.spaceId = reader.int64().toBigInt(); + break; + case /* optional string description */ 4: + message.description = reader.string(); + break; + case /* optional string emoji */ 5: + message.emoji = reader.string(); + break; + case /* optional bool is_public */ 6: + message.isPublic = reader.bool(); + break; + case /* optional int64 last_msg_id */ 7: + message.lastMsgId = reader.int64().toBigInt(); + break; + case /* Peer peer_id */ 8: + message.peerId = Peer.internalBinaryRead(reader, reader.uint32(), options, message.peerId); + break; + case /* optional int64 date */ 9: + message.date = reader.int64().toBigInt(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: Chat, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* int64 id = 1; */ + if (message.id !== 0n) + writer.tag(1, WireType.Varint).int64(message.id); + /* string title = 2; */ + if (message.title !== "") + writer.tag(2, WireType.LengthDelimited).string(message.title); + /* optional int64 space_id = 3; */ + if (message.spaceId !== undefined) + writer.tag(3, WireType.Varint).int64(message.spaceId); + /* optional string description = 4; */ + if (message.description !== undefined) + writer.tag(4, WireType.LengthDelimited).string(message.description); + /* optional string emoji = 5; */ + if (message.emoji !== undefined) + writer.tag(5, WireType.LengthDelimited).string(message.emoji); + /* optional bool is_public = 6; */ + if (message.isPublic !== undefined) + writer.tag(6, WireType.Varint).bool(message.isPublic); + /* optional int64 last_msg_id = 7; */ + if (message.lastMsgId !== undefined) + writer.tag(7, WireType.Varint).int64(message.lastMsgId); + /* Peer peer_id = 8; */ + if (message.peerId) + Peer.internalBinaryWrite(message.peerId, writer.tag(8, WireType.LengthDelimited).fork(), options).join(); + /* optional int64 date = 9; */ + if (message.date !== undefined) + writer.tag(9, WireType.Varint).int64(message.date); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message Chat + */ +export const Chat = new Chat$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class Message$Type extends MessageType { + constructor() { + super("Message", [ + { no: 1, name: "id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "from_id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 3, name: "peer_id", kind: "message", T: () => Peer }, + { no: 4, name: "chat_id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 5, name: "message", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, + { no: 6, name: "out", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }, + { no: 7, name: "date", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 8, name: "mentioned", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, + { no: 9, name: "reply_to_msg_id", kind: "scalar", opt: true, T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 10, name: "media", kind: "message", T: () => MessageMedia }, + { no: 11, name: "edit_date", kind: "scalar", opt: true, T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 12, name: "grouped_id", kind: "scalar", opt: true, T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 13, name: "attachments", kind: "message", T: () => MessageAttachments }, + { no: 14, name: "reactions", kind: "message", T: () => MessageReactions }, + { no: 15, name: "is_sticker", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, + { no: 16, name: "entities", kind: "message", T: () => MessageEntities } + ]); + } + create(value?: PartialMessage): Message { + const message = globalThis.Object.create((this.messagePrototype!)); + message.id = 0n; + message.fromId = 0n; + message.chatId = 0n; + message.out = false; + message.date = 0n; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Message): Message { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* int64 id */ 1: + message.id = reader.int64().toBigInt(); + break; + case /* int64 from_id */ 2: + message.fromId = reader.int64().toBigInt(); + break; + case /* Peer peer_id */ 3: + message.peerId = Peer.internalBinaryRead(reader, reader.uint32(), options, message.peerId); + break; + case /* int64 chat_id */ 4: + message.chatId = reader.int64().toBigInt(); + break; + case /* optional string message */ 5: + message.message = reader.string(); + break; + case /* bool out */ 6: + message.out = reader.bool(); + break; + case /* int64 date */ 7: + message.date = reader.int64().toBigInt(); + break; + case /* optional bool mentioned */ 8: + message.mentioned = reader.bool(); + break; + case /* optional int64 reply_to_msg_id */ 9: + message.replyToMsgId = reader.int64().toBigInt(); + break; + case /* optional MessageMedia media */ 10: + message.media = MessageMedia.internalBinaryRead(reader, reader.uint32(), options, message.media); + break; + case /* optional int64 edit_date */ 11: + message.editDate = reader.int64().toBigInt(); + break; + case /* optional int64 grouped_id */ 12: + message.groupedId = reader.int64().toBigInt(); + break; + case /* optional MessageAttachments attachments */ 13: + message.attachments = MessageAttachments.internalBinaryRead(reader, reader.uint32(), options, message.attachments); + break; + case /* optional MessageReactions reactions */ 14: + message.reactions = MessageReactions.internalBinaryRead(reader, reader.uint32(), options, message.reactions); + break; + case /* optional bool is_sticker */ 15: + message.isSticker = reader.bool(); + break; + case /* optional MessageEntities entities */ 16: + message.entities = MessageEntities.internalBinaryRead(reader, reader.uint32(), options, message.entities); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: Message, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* int64 id = 1; */ + if (message.id !== 0n) + writer.tag(1, WireType.Varint).int64(message.id); + /* int64 from_id = 2; */ + if (message.fromId !== 0n) + writer.tag(2, WireType.Varint).int64(message.fromId); + /* Peer peer_id = 3; */ + if (message.peerId) + Peer.internalBinaryWrite(message.peerId, writer.tag(3, WireType.LengthDelimited).fork(), options).join(); + /* int64 chat_id = 4; */ + if (message.chatId !== 0n) + writer.tag(4, WireType.Varint).int64(message.chatId); + /* optional string message = 5; */ + if (message.message !== undefined) + writer.tag(5, WireType.LengthDelimited).string(message.message); + /* bool out = 6; */ + if (message.out !== false) + writer.tag(6, WireType.Varint).bool(message.out); + /* int64 date = 7; */ + if (message.date !== 0n) + writer.tag(7, WireType.Varint).int64(message.date); + /* optional bool mentioned = 8; */ + if (message.mentioned !== undefined) + writer.tag(8, WireType.Varint).bool(message.mentioned); + /* optional int64 reply_to_msg_id = 9; */ + if (message.replyToMsgId !== undefined) + writer.tag(9, WireType.Varint).int64(message.replyToMsgId); + /* optional MessageMedia media = 10; */ + if (message.media) + MessageMedia.internalBinaryWrite(message.media, writer.tag(10, WireType.LengthDelimited).fork(), options).join(); + /* optional int64 edit_date = 11; */ + if (message.editDate !== undefined) + writer.tag(11, WireType.Varint).int64(message.editDate); + /* optional int64 grouped_id = 12; */ + if (message.groupedId !== undefined) + writer.tag(12, WireType.Varint).int64(message.groupedId); + /* optional MessageAttachments attachments = 13; */ + if (message.attachments) + MessageAttachments.internalBinaryWrite(message.attachments, writer.tag(13, WireType.LengthDelimited).fork(), options).join(); + /* optional MessageReactions reactions = 14; */ + if (message.reactions) + MessageReactions.internalBinaryWrite(message.reactions, writer.tag(14, WireType.LengthDelimited).fork(), options).join(); + /* optional bool is_sticker = 15; */ + if (message.isSticker !== undefined) + writer.tag(15, WireType.Varint).bool(message.isSticker); + /* optional MessageEntities entities = 16; */ + if (message.entities) + MessageEntities.internalBinaryWrite(message.entities, writer.tag(16, WireType.LengthDelimited).fork(), options).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message Message + */ +export const Message = new Message$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class MessageEntities$Type extends MessageType { + constructor() { + super("MessageEntities", [ + { no: 1, name: "entities", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => MessageEntity } + ]); + } + create(value?: PartialMessage): MessageEntities { + const message = globalThis.Object.create((this.messagePrototype!)); + message.entities = []; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MessageEntities): MessageEntities { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* repeated MessageEntity entities */ 1: + message.entities.push(MessageEntity.internalBinaryRead(reader, reader.uint32(), options)); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: MessageEntities, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* repeated MessageEntity entities = 1; */ + for (let i = 0; i < message.entities.length; i++) + MessageEntity.internalBinaryWrite(message.entities[i], writer.tag(1, WireType.LengthDelimited).fork(), options).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MessageEntities + */ +export const MessageEntities = new MessageEntities$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class MessageEntity$Type extends MessageType { + constructor() { + super("MessageEntity", [ + { no: 1, name: "type", kind: "enum", T: () => ["MessageEntity.Type", MessageEntity_Type, "TYPE_"] }, + { no: 2, name: "offset", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 3, name: "length", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 4, name: "mention", kind: "message", oneof: "entity", T: () => MessageEntity_MessageEntityMention }, + { no: 5, name: "text_url", kind: "message", oneof: "entity", T: () => MessageEntity_MessageEntityTextUrl }, + { no: 6, name: "pre", kind: "message", oneof: "entity", T: () => MessageEntity_MessageEntityPre } + ]); + } + create(value?: PartialMessage): MessageEntity { + const message = globalThis.Object.create((this.messagePrototype!)); + message.type = 0; + message.offset = 0n; + message.length = 0n; + message.entity = { oneofKind: undefined }; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MessageEntity): MessageEntity { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* MessageEntity.Type type */ 1: + message.type = reader.int32(); + break; + case /* int64 offset */ 2: + message.offset = reader.int64().toBigInt(); + break; + case /* int64 length */ 3: + message.length = reader.int64().toBigInt(); + break; + case /* MessageEntity.MessageEntityMention mention */ 4: + message.entity = { + oneofKind: "mention", + mention: MessageEntity_MessageEntityMention.internalBinaryRead(reader, reader.uint32(), options, (message.entity as any).mention) + }; + break; + case /* MessageEntity.MessageEntityTextUrl text_url */ 5: + message.entity = { + oneofKind: "textUrl", + textUrl: MessageEntity_MessageEntityTextUrl.internalBinaryRead(reader, reader.uint32(), options, (message.entity as any).textUrl) + }; + break; + case /* MessageEntity.MessageEntityPre pre */ 6: + message.entity = { + oneofKind: "pre", + pre: MessageEntity_MessageEntityPre.internalBinaryRead(reader, reader.uint32(), options, (message.entity as any).pre) + }; + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: MessageEntity, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* MessageEntity.Type type = 1; */ + if (message.type !== 0) + writer.tag(1, WireType.Varint).int32(message.type); + /* int64 offset = 2; */ + if (message.offset !== 0n) + writer.tag(2, WireType.Varint).int64(message.offset); + /* int64 length = 3; */ + if (message.length !== 0n) + writer.tag(3, WireType.Varint).int64(message.length); + /* MessageEntity.MessageEntityMention mention = 4; */ + if (message.entity.oneofKind === "mention") + MessageEntity_MessageEntityMention.internalBinaryWrite(message.entity.mention, writer.tag(4, WireType.LengthDelimited).fork(), options).join(); + /* MessageEntity.MessageEntityTextUrl text_url = 5; */ + if (message.entity.oneofKind === "textUrl") + MessageEntity_MessageEntityTextUrl.internalBinaryWrite(message.entity.textUrl, writer.tag(5, WireType.LengthDelimited).fork(), options).join(); + /* MessageEntity.MessageEntityPre pre = 6; */ + if (message.entity.oneofKind === "pre") + MessageEntity_MessageEntityPre.internalBinaryWrite(message.entity.pre, writer.tag(6, WireType.LengthDelimited).fork(), options).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MessageEntity + */ +export const MessageEntity = new MessageEntity$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class MessageEntity_MessageEntityMention$Type extends MessageType { + constructor() { + super("MessageEntity.MessageEntityMention", [ + { no: 1, name: "user_id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ } + ]); + } + create(value?: PartialMessage): MessageEntity_MessageEntityMention { + const message = globalThis.Object.create((this.messagePrototype!)); + message.userId = 0n; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MessageEntity_MessageEntityMention): MessageEntity_MessageEntityMention { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* int64 user_id */ 1: + message.userId = reader.int64().toBigInt(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: MessageEntity_MessageEntityMention, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* int64 user_id = 1; */ + if (message.userId !== 0n) + writer.tag(1, WireType.Varint).int64(message.userId); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MessageEntity.MessageEntityMention + */ +export const MessageEntity_MessageEntityMention = new MessageEntity_MessageEntityMention$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class MessageEntity_MessageEntityTextUrl$Type extends MessageType { + constructor() { + super("MessageEntity.MessageEntityTextUrl", [ + { no: 1, name: "url", kind: "scalar", T: 9 /*ScalarType.STRING*/ } + ]); + } + create(value?: PartialMessage): MessageEntity_MessageEntityTextUrl { + const message = globalThis.Object.create((this.messagePrototype!)); + message.url = ""; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MessageEntity_MessageEntityTextUrl): MessageEntity_MessageEntityTextUrl { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* string url */ 1: + message.url = reader.string(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: MessageEntity_MessageEntityTextUrl, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* string url = 1; */ + if (message.url !== "") + writer.tag(1, WireType.LengthDelimited).string(message.url); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MessageEntity.MessageEntityTextUrl + */ +export const MessageEntity_MessageEntityTextUrl = new MessageEntity_MessageEntityTextUrl$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class MessageEntity_MessageEntityPre$Type extends MessageType { + constructor() { + super("MessageEntity.MessageEntityPre", [ + { no: 1, name: "language", kind: "scalar", T: 9 /*ScalarType.STRING*/ } + ]); + } + create(value?: PartialMessage): MessageEntity_MessageEntityPre { + const message = globalThis.Object.create((this.messagePrototype!)); + message.language = ""; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MessageEntity_MessageEntityPre): MessageEntity_MessageEntityPre { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* string language */ 1: + message.language = reader.string(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: MessageEntity_MessageEntityPre, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* string language = 1; */ + if (message.language !== "") + writer.tag(1, WireType.LengthDelimited).string(message.language); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MessageEntity.MessageEntityPre + */ +export const MessageEntity_MessageEntityPre = new MessageEntity_MessageEntityPre$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class MessageReactions$Type extends MessageType { + constructor() { + super("MessageReactions", [ + { no: 1, name: "reactions", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => Reaction } + ]); + } + create(value?: PartialMessage): MessageReactions { + const message = globalThis.Object.create((this.messagePrototype!)); + message.reactions = []; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MessageReactions): MessageReactions { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* repeated Reaction reactions */ 1: + message.reactions.push(Reaction.internalBinaryRead(reader, reader.uint32(), options)); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: MessageReactions, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* repeated Reaction reactions = 1; */ + for (let i = 0; i < message.reactions.length; i++) + Reaction.internalBinaryWrite(message.reactions[i], writer.tag(1, WireType.LengthDelimited).fork(), options).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MessageReactions + */ +export const MessageReactions = new MessageReactions$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class Reaction$Type extends MessageType { + constructor() { + super("Reaction", [ + { no: 1, name: "emoji", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 2, name: "user_id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 3, name: "message_id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 4, name: "chat_id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 5, name: "date", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ } + ]); + } + create(value?: PartialMessage): Reaction { + const message = globalThis.Object.create((this.messagePrototype!)); + message.emoji = ""; + message.userId = 0n; + message.messageId = 0n; + message.chatId = 0n; + message.date = 0n; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Reaction): Reaction { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* string emoji */ 1: + message.emoji = reader.string(); + break; + case /* int64 user_id */ 2: + message.userId = reader.int64().toBigInt(); + break; + case /* int64 message_id */ 3: + message.messageId = reader.int64().toBigInt(); + break; + case /* int64 chat_id */ 4: + message.chatId = reader.int64().toBigInt(); + break; + case /* int64 date */ 5: + message.date = reader.int64().toBigInt(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: Reaction, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* string emoji = 1; */ + if (message.emoji !== "") + writer.tag(1, WireType.LengthDelimited).string(message.emoji); + /* int64 user_id = 2; */ + if (message.userId !== 0n) + writer.tag(2, WireType.Varint).int64(message.userId); + /* int64 message_id = 3; */ + if (message.messageId !== 0n) + writer.tag(3, WireType.Varint).int64(message.messageId); + /* int64 chat_id = 4; */ + if (message.chatId !== 0n) + writer.tag(4, WireType.Varint).int64(message.chatId); + /* int64 date = 5; */ + if (message.date !== 0n) + writer.tag(5, WireType.Varint).int64(message.date); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message Reaction + */ +export const Reaction = new Reaction$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class Member$Type extends MessageType { + constructor() { + super("Member", [ + { no: 1, name: "id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "space_id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 3, name: "user_id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 4, name: "role", kind: "enum", opt: true, T: () => ["Member.Role", Member_Role] }, + { no: 5, name: "date", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 6, name: "can_access_public_chats", kind: "scalar", T: 8 /*ScalarType.BOOL*/ } + ]); + } + create(value?: PartialMessage): Member { + const message = globalThis.Object.create((this.messagePrototype!)); + message.id = 0n; + message.spaceId = 0n; + message.userId = 0n; + message.date = 0n; + message.canAccessPublicChats = false; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Member): Member { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* int64 id */ 1: + message.id = reader.int64().toBigInt(); + break; + case /* int64 space_id */ 2: + message.spaceId = reader.int64().toBigInt(); + break; + case /* int64 user_id */ 3: + message.userId = reader.int64().toBigInt(); + break; + case /* optional Member.Role role */ 4: + message.role = reader.int32(); + break; + case /* int64 date */ 5: + message.date = reader.int64().toBigInt(); + break; + case /* bool can_access_public_chats */ 6: + message.canAccessPublicChats = reader.bool(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: Member, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* int64 id = 1; */ + if (message.id !== 0n) + writer.tag(1, WireType.Varint).int64(message.id); + /* int64 space_id = 2; */ + if (message.spaceId !== 0n) + writer.tag(2, WireType.Varint).int64(message.spaceId); + /* int64 user_id = 3; */ + if (message.userId !== 0n) + writer.tag(3, WireType.Varint).int64(message.userId); + /* optional Member.Role role = 4; */ + if (message.role !== undefined) + writer.tag(4, WireType.Varint).int32(message.role); + /* int64 date = 5; */ + if (message.date !== 0n) + writer.tag(5, WireType.Varint).int64(message.date); + /* bool can_access_public_chats = 6; */ + if (message.canAccessPublicChats !== false) + writer.tag(6, WireType.Varint).bool(message.canAccessPublicChats); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message Member + */ +export const Member = new Member$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class Space$Type extends MessageType { + constructor() { + super("Space", [ + { no: 1, name: "id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "name", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 3, name: "creator", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }, + { no: 4, name: "date", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ } + ]); + } + create(value?: PartialMessage): Space { + const message = globalThis.Object.create((this.messagePrototype!)); + message.id = 0n; + message.name = ""; + message.creator = false; + message.date = 0n; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Space): Space { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* int64 id */ 1: + message.id = reader.int64().toBigInt(); + break; + case /* string name */ 2: + message.name = reader.string(); + break; + case /* bool creator */ 3: + message.creator = reader.bool(); + break; + case /* int64 date */ 4: + message.date = reader.int64().toBigInt(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: Space, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* int64 id = 1; */ + if (message.id !== 0n) + writer.tag(1, WireType.Varint).int64(message.id); + /* string name = 2; */ + if (message.name !== "") + writer.tag(2, WireType.LengthDelimited).string(message.name); + /* bool creator = 3; */ + if (message.creator !== false) + writer.tag(3, WireType.Varint).bool(message.creator); + /* int64 date = 4; */ + if (message.date !== 0n) + writer.tag(4, WireType.Varint).int64(message.date); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message Space + */ +export const Space = new Space$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class AddReactionInput$Type extends MessageType { + constructor() { + super("AddReactionInput", [ + { no: 1, name: "emoji", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 2, name: "message_id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 3, name: "peer_id", kind: "message", T: () => InputPeer } + ]); + } + create(value?: PartialMessage): AddReactionInput { + const message = globalThis.Object.create((this.messagePrototype!)); + message.emoji = ""; + message.messageId = 0n; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: AddReactionInput): AddReactionInput { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* string emoji */ 1: + message.emoji = reader.string(); + break; + case /* int64 message_id */ 2: + message.messageId = reader.int64().toBigInt(); + break; + case /* InputPeer peer_id */ 3: + message.peerId = InputPeer.internalBinaryRead(reader, reader.uint32(), options, message.peerId); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: AddReactionInput, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* string emoji = 1; */ + if (message.emoji !== "") + writer.tag(1, WireType.LengthDelimited).string(message.emoji); + /* int64 message_id = 2; */ + if (message.messageId !== 0n) + writer.tag(2, WireType.Varint).int64(message.messageId); + /* InputPeer peer_id = 3; */ + if (message.peerId) + InputPeer.internalBinaryWrite(message.peerId, writer.tag(3, WireType.LengthDelimited).fork(), options).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message AddReactionInput + */ +export const AddReactionInput = new AddReactionInput$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class AddReactionResult$Type extends MessageType { + constructor() { + super("AddReactionResult", [ + { no: 1, name: "updates", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => Update } + ]); + } + create(value?: PartialMessage): AddReactionResult { + const message = globalThis.Object.create((this.messagePrototype!)); + message.updates = []; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: AddReactionResult): AddReactionResult { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* repeated Update updates */ 1: + message.updates.push(Update.internalBinaryRead(reader, reader.uint32(), options)); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: AddReactionResult, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* repeated Update updates = 1; */ + for (let i = 0; i < message.updates.length; i++) + Update.internalBinaryWrite(message.updates[i], writer.tag(1, WireType.LengthDelimited).fork(), options).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message AddReactionResult + */ +export const AddReactionResult = new AddReactionResult$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class DeleteReactionInput$Type extends MessageType { + constructor() { + super("DeleteReactionInput", [ + { no: 1, name: "emoji", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 2, name: "peer_id", kind: "message", T: () => InputPeer }, + { no: 3, name: "message_id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ } + ]); + } + create(value?: PartialMessage): DeleteReactionInput { + const message = globalThis.Object.create((this.messagePrototype!)); + message.emoji = ""; + message.messageId = 0n; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: DeleteReactionInput): DeleteReactionInput { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* string emoji */ 1: + message.emoji = reader.string(); + break; + case /* InputPeer peer_id */ 2: + message.peerId = InputPeer.internalBinaryRead(reader, reader.uint32(), options, message.peerId); + break; + case /* int64 message_id */ 3: + message.messageId = reader.int64().toBigInt(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: DeleteReactionInput, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* string emoji = 1; */ + if (message.emoji !== "") + writer.tag(1, WireType.LengthDelimited).string(message.emoji); + /* InputPeer peer_id = 2; */ + if (message.peerId) + InputPeer.internalBinaryWrite(message.peerId, writer.tag(2, WireType.LengthDelimited).fork(), options).join(); + /* int64 message_id = 3; */ + if (message.messageId !== 0n) + writer.tag(3, WireType.Varint).int64(message.messageId); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message DeleteReactionInput + */ +export const DeleteReactionInput = new DeleteReactionInput$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class DeleteReactionResult$Type extends MessageType { + constructor() { + super("DeleteReactionResult", [ + { no: 1, name: "updates", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => Update } + ]); + } + create(value?: PartialMessage): DeleteReactionResult { + const message = globalThis.Object.create((this.messagePrototype!)); + message.updates = []; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: DeleteReactionResult): DeleteReactionResult { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* repeated Update updates */ 1: + message.updates.push(Update.internalBinaryRead(reader, reader.uint32(), options)); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: DeleteReactionResult, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* repeated Update updates = 1; */ + for (let i = 0; i < message.updates.length; i++) + Update.internalBinaryWrite(message.updates[i], writer.tag(1, WireType.LengthDelimited).fork(), options).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message DeleteReactionResult + */ +export const DeleteReactionResult = new DeleteReactionResult$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class MessageAttachments$Type extends MessageType { + constructor() { + super("MessageAttachments", [ + { no: 1, name: "attachments", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => MessageAttachment } + ]); + } + create(value?: PartialMessage): MessageAttachments { + const message = globalThis.Object.create((this.messagePrototype!)); + message.attachments = []; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MessageAttachments): MessageAttachments { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* repeated MessageAttachment attachments */ 1: + message.attachments.push(MessageAttachment.internalBinaryRead(reader, reader.uint32(), options)); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: MessageAttachments, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* repeated MessageAttachment attachments = 1; */ + for (let i = 0; i < message.attachments.length; i++) + MessageAttachment.internalBinaryWrite(message.attachments[i], writer.tag(1, WireType.LengthDelimited).fork(), options).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MessageAttachments + */ +export const MessageAttachments = new MessageAttachments$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class MessageAttachment$Type extends MessageType { + constructor() { + super("MessageAttachment", [ + { no: 4, name: "id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "external_task", kind: "message", oneof: "attachment", T: () => MessageAttachmentExternalTask }, + { no: 3, name: "url_preview", kind: "message", oneof: "attachment", T: () => UrlPreview } + ]); + } + create(value?: PartialMessage): MessageAttachment { + const message = globalThis.Object.create((this.messagePrototype!)); + message.id = 0n; + message.attachment = { oneofKind: undefined }; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MessageAttachment): MessageAttachment { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* int64 id */ 4: + message.id = reader.int64().toBigInt(); + break; + case /* MessageAttachmentExternalTask external_task */ 2: + message.attachment = { + oneofKind: "externalTask", + externalTask: MessageAttachmentExternalTask.internalBinaryRead(reader, reader.uint32(), options, (message.attachment as any).externalTask) + }; + break; + case /* UrlPreview url_preview */ 3: + message.attachment = { + oneofKind: "urlPreview", + urlPreview: UrlPreview.internalBinaryRead(reader, reader.uint32(), options, (message.attachment as any).urlPreview) + }; + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: MessageAttachment, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* int64 id = 4; */ + if (message.id !== 0n) + writer.tag(4, WireType.Varint).int64(message.id); + /* MessageAttachmentExternalTask external_task = 2; */ + if (message.attachment.oneofKind === "externalTask") + MessageAttachmentExternalTask.internalBinaryWrite(message.attachment.externalTask, writer.tag(2, WireType.LengthDelimited).fork(), options).join(); + /* UrlPreview url_preview = 3; */ + if (message.attachment.oneofKind === "urlPreview") + UrlPreview.internalBinaryWrite(message.attachment.urlPreview, writer.tag(3, WireType.LengthDelimited).fork(), options).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MessageAttachment + */ +export const MessageAttachment = new MessageAttachment$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class UrlPreview$Type extends MessageType { + constructor() { + super("UrlPreview", [ + { no: 1, name: "id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "url", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, + { no: 3, name: "site_name", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, + { no: 4, name: "title", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, + { no: 5, name: "description", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, + { no: 6, name: "photo", kind: "message", T: () => Photo }, + { no: 7, name: "duration", kind: "scalar", opt: true, T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ } + ]); + } + create(value?: PartialMessage): UrlPreview { + const message = globalThis.Object.create((this.messagePrototype!)); + message.id = 0n; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: UrlPreview): UrlPreview { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* int64 id */ 1: + message.id = reader.int64().toBigInt(); + break; + case /* optional string url */ 2: + message.url = reader.string(); + break; + case /* optional string site_name */ 3: + message.siteName = reader.string(); + break; + case /* optional string title */ 4: + message.title = reader.string(); + break; + case /* optional string description */ 5: + message.description = reader.string(); + break; + case /* optional Photo photo */ 6: + message.photo = Photo.internalBinaryRead(reader, reader.uint32(), options, message.photo); + break; + case /* optional int64 duration */ 7: + message.duration = reader.int64().toBigInt(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: UrlPreview, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* int64 id = 1; */ + if (message.id !== 0n) + writer.tag(1, WireType.Varint).int64(message.id); + /* optional string url = 2; */ + if (message.url !== undefined) + writer.tag(2, WireType.LengthDelimited).string(message.url); + /* optional string site_name = 3; */ + if (message.siteName !== undefined) + writer.tag(3, WireType.LengthDelimited).string(message.siteName); + /* optional string title = 4; */ + if (message.title !== undefined) + writer.tag(4, WireType.LengthDelimited).string(message.title); + /* optional string description = 5; */ + if (message.description !== undefined) + writer.tag(5, WireType.LengthDelimited).string(message.description); + /* optional Photo photo = 6; */ + if (message.photo) + Photo.internalBinaryWrite(message.photo, writer.tag(6, WireType.LengthDelimited).fork(), options).join(); + /* optional int64 duration = 7; */ + if (message.duration !== undefined) + writer.tag(7, WireType.Varint).int64(message.duration); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message UrlPreview + */ +export const UrlPreview = new UrlPreview$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class MessageAttachmentExternalTask$Type extends MessageType { + constructor() { + super("MessageAttachmentExternalTask", [ + { no: 1, name: "id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "task_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 3, name: "application", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 4, name: "title", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 5, name: "status", kind: "enum", T: () => ["MessageAttachmentExternalTask.Status", MessageAttachmentExternalTask_Status, "STATUS_"] }, + { no: 6, name: "assigned_user_id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 7, name: "url", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 8, name: "number", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 9, name: "date", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ } + ]); + } + create(value?: PartialMessage): MessageAttachmentExternalTask { + const message = globalThis.Object.create((this.messagePrototype!)); + message.id = 0n; + message.taskId = ""; + message.application = ""; + message.title = ""; + message.status = 0; + message.assignedUserId = 0n; + message.url = ""; + message.number = ""; + message.date = 0n; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MessageAttachmentExternalTask): MessageAttachmentExternalTask { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* int64 id */ 1: + message.id = reader.int64().toBigInt(); + break; + case /* string task_id */ 2: + message.taskId = reader.string(); + break; + case /* string application */ 3: + message.application = reader.string(); + break; + case /* string title */ 4: + message.title = reader.string(); + break; + case /* MessageAttachmentExternalTask.Status status */ 5: + message.status = reader.int32(); + break; + case /* int64 assigned_user_id */ 6: + message.assignedUserId = reader.int64().toBigInt(); + break; + case /* string url */ 7: + message.url = reader.string(); + break; + case /* string number */ 8: + message.number = reader.string(); + break; + case /* int64 date */ 9: + message.date = reader.int64().toBigInt(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: MessageAttachmentExternalTask, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* int64 id = 1; */ + if (message.id !== 0n) + writer.tag(1, WireType.Varint).int64(message.id); + /* string task_id = 2; */ + if (message.taskId !== "") + writer.tag(2, WireType.LengthDelimited).string(message.taskId); + /* string application = 3; */ + if (message.application !== "") + writer.tag(3, WireType.LengthDelimited).string(message.application); + /* string title = 4; */ + if (message.title !== "") + writer.tag(4, WireType.LengthDelimited).string(message.title); + /* MessageAttachmentExternalTask.Status status = 5; */ + if (message.status !== 0) + writer.tag(5, WireType.Varint).int32(message.status); + /* int64 assigned_user_id = 6; */ + if (message.assignedUserId !== 0n) + writer.tag(6, WireType.Varint).int64(message.assignedUserId); + /* string url = 7; */ + if (message.url !== "") + writer.tag(7, WireType.LengthDelimited).string(message.url); + /* string number = 8; */ + if (message.number !== "") + writer.tag(8, WireType.LengthDelimited).string(message.number); + /* int64 date = 9; */ + if (message.date !== 0n) + writer.tag(9, WireType.Varint).int64(message.date); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MessageAttachmentExternalTask + */ +export const MessageAttachmentExternalTask = new MessageAttachmentExternalTask$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class MessageMedia$Type extends MessageType { + constructor() { + super("MessageMedia", [ + { no: 1, name: "photo", kind: "message", oneof: "media", T: () => MessagePhoto }, + { no: 2, name: "video", kind: "message", oneof: "media", T: () => MessageVideo }, + { no: 3, name: "document", kind: "message", oneof: "media", T: () => MessageDocument } + ]); + } + create(value?: PartialMessage): MessageMedia { + const message = globalThis.Object.create((this.messagePrototype!)); + message.media = { oneofKind: undefined }; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MessageMedia): MessageMedia { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* MessagePhoto photo */ 1: + message.media = { + oneofKind: "photo", + photo: MessagePhoto.internalBinaryRead(reader, reader.uint32(), options, (message.media as any).photo) + }; + break; + case /* MessageVideo video */ 2: + message.media = { + oneofKind: "video", + video: MessageVideo.internalBinaryRead(reader, reader.uint32(), options, (message.media as any).video) + }; + break; + case /* MessageDocument document */ 3: + message.media = { + oneofKind: "document", + document: MessageDocument.internalBinaryRead(reader, reader.uint32(), options, (message.media as any).document) + }; + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: MessageMedia, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* MessagePhoto photo = 1; */ + if (message.media.oneofKind === "photo") + MessagePhoto.internalBinaryWrite(message.media.photo, writer.tag(1, WireType.LengthDelimited).fork(), options).join(); + /* MessageVideo video = 2; */ + if (message.media.oneofKind === "video") + MessageVideo.internalBinaryWrite(message.media.video, writer.tag(2, WireType.LengthDelimited).fork(), options).join(); + /* MessageDocument document = 3; */ + if (message.media.oneofKind === "document") + MessageDocument.internalBinaryWrite(message.media.document, writer.tag(3, WireType.LengthDelimited).fork(), options).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MessageMedia + */ +export const MessageMedia = new MessageMedia$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class MessagePhoto$Type extends MessageType { + constructor() { + super("MessagePhoto", [ + { no: 1, name: "photo", kind: "message", T: () => Photo } + ]); + } + create(value?: PartialMessage): MessagePhoto { + const message = globalThis.Object.create((this.messagePrototype!)); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MessagePhoto): MessagePhoto { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* Photo photo */ 1: + message.photo = Photo.internalBinaryRead(reader, reader.uint32(), options, message.photo); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: MessagePhoto, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* Photo photo = 1; */ + if (message.photo) + Photo.internalBinaryWrite(message.photo, writer.tag(1, WireType.LengthDelimited).fork(), options).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MessagePhoto + */ +export const MessagePhoto = new MessagePhoto$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class MessageVideo$Type extends MessageType { + constructor() { + super("MessageVideo", [ + { no: 1, name: "video", kind: "message", T: () => Video } + ]); + } + create(value?: PartialMessage): MessageVideo { + const message = globalThis.Object.create((this.messagePrototype!)); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MessageVideo): MessageVideo { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* Video video */ 1: + message.video = Video.internalBinaryRead(reader, reader.uint32(), options, message.video); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: MessageVideo, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* Video video = 1; */ + if (message.video) + Video.internalBinaryWrite(message.video, writer.tag(1, WireType.LengthDelimited).fork(), options).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MessageVideo + */ +export const MessageVideo = new MessageVideo$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class MessageDocument$Type extends MessageType { + constructor() { + super("MessageDocument", [ + { no: 1, name: "document", kind: "message", T: () => Document } + ]); + } + create(value?: PartialMessage): MessageDocument { + const message = globalThis.Object.create((this.messagePrototype!)); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MessageDocument): MessageDocument { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* Document document */ 1: + message.document = Document.internalBinaryRead(reader, reader.uint32(), options, message.document); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: MessageDocument, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* Document document = 1; */ + if (message.document) + Document.internalBinaryWrite(message.document, writer.tag(1, WireType.LengthDelimited).fork(), options).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MessageDocument + */ +export const MessageDocument = new MessageDocument$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class Video$Type extends MessageType