So before we go on and create the actual stream player, I want to point out one thing that I've noticed while looking back at our previous module. And that is that inside of our lib folder, specifically our follow service and our recommended service, we include the entire stream. And what we do then is we pass that to a client component. So inside of this sidebar, sidebar by itself is a server component. So this is secure, right?
But then we pass the entire data to client components, right? So this stream model could technically be visible inside of a JavaScript bundle. I don't have the skills to extract that but I'm pretty sure there are some malicious users or maybe just white hat hackers who do this for a living who could point out this mistake for us. So this is what I want to do instead. I don't want to pass the entire stream model because in our schema our stream model has some pretty sensitive information here which could be used by other users.
So I'm gonna try and fix that by first going inside of the recommended service and instead of just plainly doing include the stream true what I'm gonna do is I'm gonna extend this and use select and just write is live to be true like this Because that's the only piece of information we need and it's not malicious or harmful to send this to the client. And let's modify the same thing in the else clause here. So instead of stream true let's do select isLive true like this. And now let's go back inside of our sidebar index. And I believe we should now be having an error here for our recommended data.
So let's go inside of the recommended field here and I'm gonna go ahead and modify this to not be a stream entirely. Instead just isLive which is a boolean. And I think that's going to fix the TypeScript error. As you can see it fixed it for me. So this is now working and our code is also working like that.
Great! So let's do the same thing inside of the follow service. So go inside of the follow service here and in here in the get followed users we do the same thing we include the entire stream right But we are passing some sensitive information to the client. So instead let's change this to be select, isLive true, like that. And then we have to go back inside of our sidebar index where we now have the TypeScript error and fix the following component.
So go inside of following and let's change the stream to not be the entire object instead just isLive boolean. And now you can remove the stream from your imports in the following component and in the recommended component like this. Great, so I believe that now this should still be working. So you can go ahead and start the stream if you want, but let me just do that. Yeah, I'm gonna go ahead and click start streaming.
So I just clicked it in my other browser, right? You have the previous two modules where I showed you how to start your stream. So just go into OBS and click start streaming. And there we go, it's still working. And now I'm going to click stop streaming.
And I believe that after a couple of refreshes here, it should go back to offline. Let's go ahead and wait for it you know it's there we go okay great so we fix that little security issue that we have and now our code is secure because we are not including the sensitive information like ingress ID, server URL and the stream key. Great! So now that we resolved that what I want to do is I want to go inside of my app folder, browse, username, sorry not browse my apologies, dashboard, you username and home page.vsx. So right now all we do here is we have the creator page right?
Well instead of that what I want to do is I want to go ahead and render the following. So let's go ahead and do a class name here. And let's do h-full, like that. And then what I want to do is I want to ensure that whoever is visiting this page is the proper user. So let's mark this as an asynchronous page here and let's add the interface to accept this username prop from the URL.
So interface creator page props will have params and those params have the username, which is a string, and we know it has a username exactly because that's what we named this folder, which is encapsulating this. Then we can go ahead and assign those props, so create our page props, and we can extract the params. And then let's go ahead and do const external user to be await current user from clerk-nextjs. So make sure you add this import. And then let's go ahead and write const user to be await getUserByUsername from libUserService.
So we already have that created. If you don't, make sure you create the user service and add this, even though you should be having it because we created this together, but just in case you somehow missed it, you can either code it right here or go inside of my GitHub. And let's do getByUsername, params.username, like that. And now let's do if there is no user, or if user.externalUserId does not match the external user? ID or if there is no user dot stream.
So if any of those cases happen, we're going to throw new error unauthorized like that. And you should probably be getting this error for the user stream. And I think that's because we have to include the stream from this get a user by username service. So let's go ahead and click here, get user by username service. And let's add include here, stream to be true like that.
And there we go. Now we don't have that error. Perfect. So we're going to need the entire stream object for this one. Because this, you can see that we are going to redirect immediately if the user who this stream does not belong to is trying to load this page so we don't have to worry about security on this part because this will handle that for us And instead what we can do here is render a component stream player which is going to be a self-closing tag and of course it does not exist yet but it is going to exist in a second and let's go ahead and do the following let's pass in the user to be user stream to be user.stream like that and now we have to go ahead and create the stream player component and the good thing about this component is that we're gonna create it once and then we're going to reuse it both in the creator dashboard and also in the viewer browse page.
So the place for that component is inside of our components folder. So inside of here, create stream-player.tsx like this. And let's go ahead and mark this as use client. Let's export const stream player and let's return a div stream player. Now go back inside of your dashboard username home page here and import stream player from s slash components stream player like that.
Now what I'm going to do is log in and go to my creator dashboard so that we can actually see this text. Alright so make sure you click on the little clapboard icon in the navbar and you should be seeing the text stream player and now what we have to do is we have to create an interface to accept those props which we've written right here so let's create an interface stream player props user is going to be a type of user from Prisma client, which is going to have stream, which is a type of stream from Prisma client like that, or now like this. And let's also go ahead and include stream itself as a prop. We already have stream imported from above and let's do isFollowing to be a boolean like that. And here's the thing.
So, well, let's go ahead and assign this. So, stream player props like that and go back inside of this page and you can see we still have an error that's because we are missing the is following prop so in the viewer page is following is going to be dynamic right we're going to use that server we have the following service So we're going to simply use that constant is following. But in this case this is our creator dashboard. So we already know that this is going to be our stream. So what we can do is we can just hard code this like this or you can pass in true explicitly if you like it more that way.
So I'm going to use this method and as you can see we no longer have any errors inside of our stream player. Great! So now let's go ahead and let's extract this. So user stream and is following from the props here and what we have to do now is we have to create a hook called use viewer token right so I'm gonna go ahead well I'm not gonna write anything here instead let's go ahead and create so just save this file stream player we're gonna create a new folder on the root of our application called hooks and then inside I'm going to create a new file use viewer token dot ts so this is going to be a hook which is going to be used to create the identity of the user that is trying to watch our stream. So for that, we're going to need to install a package.
So this is what I have running. I have running my npm run dev. That's this right here. And I have running my ngrok here. And this is my other terminal where I'm simply going to write npm install jvt-decode.
So make sure you install this package. And now let's go ahead and let's import everything we need. So we need toast from Sonar. We need useEffect and useState from React. And we need JVTPayload and JVTDecode from JVTDecode.
So JVTPayload starts with a capital J because it is a interface and this is a function so it starts with a lowercase. Make sure you don't do the mistake in the imports here. And now let's do export const useViewerToken to accept the host identity which is a string. And now let's create states for the token and set token to be useState and empty by default. Let's do the same thing for name and setName to be useState and empty by default.
And let's create the identity and set identity from useState like this. Great! And what we have to do now is we have to create our useEffect hook which is going to run once and create and fill out this information. So using this information, we're going to have info of who is watching the stream, what is their token, what is their identity, what is their name. And using those information, we're later going to be able to kick users or even further if you want to develop these applications you can grant them some additional permissions like joining the stream right so that's why we need this information here and host identity prop is going to be the identity of the streamer we are trying to watch right so that's going to be their id so let's go ahead and do const create token to be an asynchronous function like this and let's go ahead and open a try and catch block and what we have to do in here so let me just go to catch here and I'm going to very simply do toast.error something went wrong so in case we are not able to create the token and what we're going to do here is do const viewerToken to be await createViewerToken but we don't have this function yet so we have to go ahead and create it so let's go inside of our actions So there's gonna be a server action and let's create a new file token.ts inside of our actions So just leave this error like this for now because we're now gonna create that function Since this is a action mark it as use server and go ahead and we have to install a package called uuid so let's do npm install uuid like that and let's see if it comes with the types so import v4 from uuid and it doesn't have the types so let's go ahead and do the following we're gonna do npm install dash d add types slash uuid like that.
So make sure you add that and now this error should go away and you should have the types. Great and now let's import access token from lightkit server SDK. Let's go ahead and import getSelf from libalph service. Let's go ahead and import getUserBy. Oh, we only have getUserBy username.
So we have to go back inside of our user service and we have to create getUserById, right? So this should be getUserById. So leave this token as it is for now and we have to go back inside of our lib inside of the user service. My apologies for not doing this beforehand. And let's just go ahead and write export const getUserById to be an asynchronous function which accepts the id which is a string and simply gets the user using await database find sorry user find unique, where we have a matching ID.
And let's also add include stream to be true. So a very simple function here and let's return the user. That's it. That's our new function inside of the user service where we already have getUserByUsername. And now we can go back inside of our actions token.ts and instead of importing this, we can now import getUserById like that because we're gonna pass in the host identity which is their ID and let's do export const create viewer token asynchronous function which is trying to connect to host identity which is going to be the id of the user which is who is streaming and now in here first let's try and see if the user that is trying to watch this stream is logged in or logged out because we are going to allow guests So let's do let self and then let's open a try and catch block inside of try let's attempt to assign a wait get self to the self constant else if this function fails, meaning we are not logged in, we're gonna create a mock ID.
So const ID is gonna be equal to v4 from UUID. So that's gonna create the guest ID. And then what we're gonna do is const username is gonna be guest and then add a little hashtag here and then we're simply gonna use math.random times 1000 right so we're going to generate a random guest ID so it's gonna be something like guest 1 2 or 1 2 3 4 something like that that's gonna be the random username of the guest that is joining. And then we can do self is an object, which has the ID and the username. So this way we handled logged out users.
Great. And now let's do, let's find the host who is streaming. So const host is equal to await get user by ID host identity. Like that. And then if there is no host using this identity we're going to throw new error user not found Then let's check if we are blocked from watching this stream, So let's do const isBlocked, we await isBlockedByUser, which you can import from our block service.
We already did that when we implemented the blocking functionality. So we're going to check if this host has blocked that. If it did, we're not gonna be able to create the token. So if we are blocked from watching this user, we are simply gonna throw new error, user is blocked. And what that's gonna do inside of our hooks, inside of the use viewer token, is gonna go inside of the cache and just simply throw an error something went wrong you cannot watch this user, right?
So go back inside of your actions folder inside of the token here and now let's go further so let's check if the host themselves is trying to watch this so we're gonna write the const is host self.id is equal to host.id like that and then let's go ahead and finally create the access token to watch this stream with all of this information that we have. So cons token is await sorry new access token which we have imported from LiveKit server SDK right and let's pass in process.environment.livekit underscore api underscore key and process.environment.livekit process.environment.livekit-api-key and process.environment.livekit-api-secret and as always double check that you passed this correctly from your environment file right so you should have the livekit API key copied from here and pasted here and you should have the livekit API secret copied from here and pasted here and if you want to you can even add this exclamation points at the end if you're having any errors here. Great and now we're going to add some configuration for the access token. So that's going to be the identity. So if is host, we have to modify the identity a little bit because in our action to create the ingress we already have in here, you can see that we already have the participant identity to be our ID.
So we cannot have the same ID twice in one live stream. So we're going to have to modify the identity inside of the token action here if the host is watching. So we're simply going to add a little prefix here host self.id otherwise self.id very simple and then name is going to be self.username so that's either going to be the currently logged in user right or this randomly generated guest username great and finally now we are ready to add some permissions to this token. So token.addGrant, room they're trying to join is going to be host.id. Room join field is going to be true, can publish is going to be false and can publish data is going to be true.
Like that and all we have to do now is return await promise resolve token.toJVT. Like that. Great, there we go. We now have creative viewer token and what we can do now is we can head back inside of our hooks use viewer token here and we can import create viewer token from actions token So make sure you add this new import here. So we are back inside of our hooks useViewer hook here and what we have to do inside is pass the host identity where we are trying to join.
And then once we have the viewer token, let's simply do set token to be viewer token. So that's going to be the entire JVT token. And now we're going to decode it, so decoded token to be jvt decode which we imported from this package right here. So jvt decode, pass in the viewer token and let's write as JVT payload and open an object and write name to be an optional property which is a string so let me zoom out so you can see it in one line so decoded token is JVT decode as JVT payload and an object with an optional property of name which is a string. So make sure you have JVT payload and JVT decode imported from the same package.
And now that we have our decoded token, let's check, let's extract the name which is the coded token question mark dot name and let's get the identity which is the coded token question mark actually it's not question mark it definitely exists but it is under dot jte like that if we have the identity let's simply do set identity to the constant and if we have name let's go ahead and set the name to the name like that perfect And what we have to do now inside of the useEffect is actually call this createToken function. So go where it ends here and simply do createToken like this. And what we have to put inside of the dependency array is hostIdentity. And then we have to return something from this hook so let's go ahead and return token name and identity like this. Great so now we have our use viewer token here and now we are ready to add this inside of our stream player component so let's go inside of the components folder and find the stream player here and let's go ahead and do const useViewerToken which you can import from hooks useViewerToken and inside we're going to pass in user.id like that and then from useViewerToken we can extract the token name and the identity So all of this is the information of the person that is trying to watch the stream that this user is currently streaming.
And then what we can do is if we have no token, or if we have no name, or if we have no identity, let's go ahead and let's return a div saying cannot watch the stream like this and let's go ahead inside of here and we're gonna watch allowed to watch the stream like this and now if you go ahead and refresh here at first you should have cannot watch the stream and then you're going to get allowed to watch the stream right because what we're doing is we are firing this use viewer token here and in here we are calling a server action right so make sure that you added use server here at the top and in here we are doing all of that necessary information to create the identity of the person that is trying to watch and then finally inside of our stream player if we receive the token, the name and the identity it means that we have all the information we need to give this user access to the stream. Great! So just make sure that when you refresh for a second you should have cannot watch the stream and then it should change to allowed to watch the stream.
If you're having any issues here you can go ahead and add a console log here and log the token, the name and the identity here and then just go ahead and look at them inside of your inside of your inspect element and all of them should be filled and if one of them is not filled that's how you can debug so now you know if something is missing, right? So you can go ahead and trace your steps back in the user viewer token for example and add some console logs here for example. Log the name and identity. See if this function fires. See if this function fires right.
So that's only if you're having any issues with this. If it's working great no need to modify anything perfect so we did that and what we're going to do next in the stream player is we're going to go ahead and start to style this primarily we're going to go ahead and create our video component which is going to render the connecting status the offline status of the streamer and the actual video status of the streamer.