Live video on the AT Protocol
79
fork

Configure Feed

Select the types of activity you want to include in your feed.

atproto: fix stream.place --> app oauth redirect

See merge request streamplace/streamplace!92

Changelog: feature

+87 -11
+1 -1
js/app/features/bluesky/blueskyProvider.tsx
··· 36 36 } 37 37 } 38 38 } 39 - }, [url]); 39 + }, [url, lastLink]); 40 40 41 41 useEffect(() => { 42 42 if (oauthSession && !userProfile) {
+1
js/app/features/bluesky/blueskySlice.tsx
··· 219 219 220 220 oauthCallback: create.asyncThunk( 221 221 async (url: string, thunkAPI) => { 222 + console.log("oauthCallback", url); 222 223 if (!url.includes("?")) { 223 224 throw new Error("No query params"); 224 225 }
+1 -2
js/app/features/platform/platformSlice.native.tsx
··· 1 1 import { openAuthSessionAsync } from "expo-web-browser"; 2 2 import { createAppSlice } from "../../hooks/createSlice"; 3 - import { oauthCallback } from "features/bluesky/blueskySlice"; 4 3 import messaging from "@react-native-firebase/messaging"; 5 4 import { Platform, PermissionsAndroid } from "react-native"; 6 5 import { ··· 9 8 PlatformState, 10 9 } from "./shared"; 11 10 import { BlueskyState } from "features/bluesky/blueskyTypes"; 11 + import { oauthCallback } from "features/bluesky/blueskySlice"; 12 12 13 13 const checkApplicationPermission = async () => { 14 14 if (Platform.OS === "android") { ··· 51 51 }), 52 52 openLoginLink: create.asyncThunk( 53 53 async (url: string, thunkAPI) => { 54 - console.log("openLoginLink", url); 55 54 const res = await openAuthSessionAsync(url); 56 55 if (res.type === "success") { 57 56 thunkAPI.dispatch(oauthCallback(res.url));
+1
js/app/src/router.tsx
··· 325 325 options={{ 326 326 drawerLabel: () => null, 327 327 drawerItemStyle: { display: "none" }, 328 + headerShown: false, 328 329 }} 329 330 /> 330 331 <Drawer.Screen
+5
js/app/src/screens/app-return.native.tsx
··· 1 + import { Redirect } from "components/aqlink"; 2 + 3 + export default function AppReturnScreen({ route }) { 4 + return <Redirect to={{ screen: "Home" }} />; 5 + }
+1 -1
js/app/tamagui.config.ts
··· 95 95 ...configBase.themes, 96 96 dark: { 97 97 ...configBase.themes.dark, 98 - accentColor: "rgb(189 110 134)", 98 + accentColor: "rgb(189, 110, 134)", 99 99 accentBackground: "rgb(17, 49, 35)", 100 100 background2: "rgb(18, 18, 18)", 101 101 },
+2 -1
pkg/api/api.go
··· 110 110 apiRouter.GET("/api/playback/:user/stream.webm", a.HandleMKVPlayback(ctx)) 111 111 apiRouter.GET("/api/playback/:user/hls/:file", a.HandleHLSPlayback(ctx)) 112 112 apiRouter.GET("/api/playback/:user/stream.jpg", a.HandleThumbnailPlayback(ctx)) 113 + apiRouter.GET("/api/app-return/*anything", a.HandleAppReturn(ctx)) 113 114 apiRouter.POST("/api/playback/:user/webrtc", a.HandleWebRTCPlayback(ctx)) 114 115 apiRouter.POST("/api/ingest/webrtc", a.HandleWebRTCIngest(ctx)) 115 116 apiRouter.POST("/api/player-event", a.HandlePlayerEvent(ctx)) ··· 449 450 return 450 451 } 451 452 452 - meta := atproto.GetMetadata(host, platform) 453 + meta := atproto.GetMetadata(host, platform, a.CLI.AppBundleID) 453 454 bs, err := json.Marshal(meta) 454 455 if err != nil { 455 456 apierrors.WriteHTTPInternalServerError(w, "could not marshal metadata", err)
+26
pkg/api/app-return.go
··· 1 + package api 2 + 3 + import ( 4 + "context" 5 + _ "embed" 6 + "net/http" 7 + "strings" 8 + 9 + "github.com/julienschmidt/httprouter" 10 + apierrors "stream.place/streamplace/pkg/errors" 11 + ) 12 + 13 + //go:embed app-return.html 14 + var appReturnHTML string 15 + 16 + func (a *StreamplaceAPI) HandleAppReturn(ctx context.Context) httprouter.Handle { 17 + return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) { 18 + if a.CLI.AppBundleID == "" { 19 + apierrors.WriteHTTPNotImplemented(w, "server has no --app-bundle-id set", nil) 20 + return 21 + } 22 + html := strings.Replace(appReturnHTML, "APP_BUNDLE_ID_REPLACE_ME", a.CLI.AppBundleID, 1) 23 + w.Header().Set("Content-Type", "text/html") 24 + w.Write([]byte(html)) 25 + } 26 + }
+45
pkg/api/app-return.html
··· 1 + <!doctype html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 + <title>Streamplace App Return</title> 7 + <style> 8 + html, 9 + body { 10 + height: 100%; 11 + width: 100%; 12 + margin: 0; 13 + padding: 0; 14 + background-color: black; 15 + display: flex; 16 + align-items: center; 17 + justify-content: center; 18 + } 19 + button { 20 + background-color: rgb(189, 110, 134); 21 + color: white; 22 + padding: 10px 20px; 23 + border-radius: 5px; 24 + border: none; 25 + font-size: 1.5rem; 26 + font-weight: bold; 27 + cursor: pointer; 28 + } 29 + </style> 30 + </head> 31 + <body> 32 + <button>Complete Login</button> 33 + </body> 34 + <script> 35 + const appBundleId = "APP_BUNDLE_ID_REPLACE_ME"; 36 + // This should work. It does work on iOS. On Android, it causes the token to be 37 + // authorized twice? I don't know why. I spent two hours trying to figure out why 38 + // without luck. You're welcome to try more if you want! In the meantime, using 39 + // an annoying button makes it work every time. 40 + // document.location.href = `${appBundleId}:/${window.location.search}`; 41 + document.querySelector("button").addEventListener("click", () => { 42 + document.location.href = `${appBundleId}:/${window.location.search}`; 43 + }); 44 + </script> 45 + </html>
+2 -6
pkg/atproto/client_metadata.go
··· 2 2 3 3 import ( 4 4 "fmt" 5 - "slices" 6 - "strings" 7 5 ) 8 6 9 7 var AllowedPlatforms = []string{"ios", "android", "web"} ··· 44 42 return &b 45 43 } 46 44 47 - func GetMetadata(host string, platform string) *OAuthClientMetadata { 45 + func GetMetadata(host string, platform string, appBundleId string) *OAuthClientMetadata { 48 46 meta := &OAuthClientMetadata{ 49 47 ClientID: fmt.Sprintf("https://%s/api/atproto-oauth/%s", host, platform), 50 48 ClientURI: fmt.Sprintf("https://%s", host), ··· 60 58 meta.RedirectURIs = []string{fmt.Sprintf("https://%s/login", host)} 61 59 meta.ApplicationType = "web" 62 60 } else { 63 - hostParts := strings.Split(host, ".") 64 - slices.Reverse(hostParts) 65 - meta.RedirectURIs = []string{fmt.Sprintf("%s:/login", strings.Join(hostParts, "."))} 61 + meta.RedirectURIs = []string{fmt.Sprintf("https://%s/api/app-return/%s", host, appBundleId)} 66 62 meta.ApplicationType = "native" 67 63 } 68 64 return meta
+1
pkg/cmd/streamplace.go
··· 114 114 fs.StringVar(&cli.PKCS11TokenSerial, "pkcs11-token-serial", "", "serial number of PKCS11 token (only use one of slot, label, or serial)") 115 115 fs.StringVar(&cli.PKCS11KeypairLabel, "pkcs11-keypair-label", "", "label of signing keypair on PKCS11 token") 116 116 fs.StringVar(&cli.PKCS11KeypairID, "pkcs11-keypair-id", "", "id of signing keypair on PKCS11 token") 117 + fs.StringVar(&cli.AppBundleID, "app-bundle-id", "", "bundle id of an app that we facilitate oauth login for") 117 118 fs.StringVar(&cli.StreamerName, "streamer-name", "", "name of the person streaming from this streamplace node") 118 119 fs.StringVar(&cli.FrontendProxy, "dev-frontend-proxy", "", "(FOR DEVELOPMENT ONLY) proxy frontend requests to this address instead of using the bundled frontend") 119 120 cli.StringSliceFlag(fs, &cli.AllowedStreams, "allowed-streams", "", "if set, only allow these addresses or atproto DIDs to upload to this node")
+1
pkg/config/config.go
··· 78 78 Peers []string 79 79 TestStream bool 80 80 FrontendProxy string 81 + AppBundleID string 81 82 82 83 dataDirFlags []*string 83 84 }