microsoft / live-share-sdk

A framework for building collaborative Microsoft Teams and M365 experiences.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[Bug]: live-share-react: "Cannot call changeState when liveState is undefined"

nmtoblum opened this issue · comments

Dear live-share-sdk team,

I've been using the library a lot in the last few weeks and wanted to say thank you for your work. With it you can build really great apps that are a lot of fun. I'm still struggling with some of the implementation details with React, so I wanted to try live-share-react, but I haven't had any success yet.

  • I have reviewed the FAQ and known issues and did not find my topic

Describe the bug

I built a minimal example (based on vite+react+ts) based on your 04.live-share-react example. When I try to work with LiveState objects I get the error message: Cannot call changeState when liveState is undefined

This happens everytime I try to manipulate the LiveState.

Often but not always I get different error messages on initialization (happens before working with the shared objects).

  • provided user was not an "AzureUser"
  • Warning: Error in processing request : interactive.getClientInfo with error Unhandled message
  • Or something like this:
    error1

To Reproduce

I have published the minimal code and manifest used here: https://github.com/toblum/spotalot/tree/live-share-react

Expected behavior

Should be able to change the LiveState

Screenshots

see above

Desktop(s) (please complete the following information):

  • OS: Mac OS / Win 11
  • Browser: Chrome
  • Version: Latest

I would be great to get an idea what is going wrong here in my example.

Best regards
Tobias

One thing I wanted to add: To get a better understanding what happens here and to reduce complexity, I took another step back and tried to implement a similar minimal solution with live-share-turbo. It's in this branch: https://github.com/toblum/spotalot/tree/live-share-turbo

When running this code I always get this error message:
image

This happens every time when this line is executed: https://github.com/toblum/spotalot/blob/c95e8e6de9b6c2ce7a5382711f186703ee98e8d7/client/src/pages/SidePanel.tsx#L19

I think that could be related as I saw this error also when using live-share-react.

Greetings
Tobias

And I even went back one more step and tried the same as vanilla live-share:
https://github.com/toblum/spotalot/tree/minimal

Here I always get the error: Error: Registry does not contain entry for the package

image

Now I'm at the end of my rope. I have no idea what I did wrong here.

Would be great if you could give me a hint what's happening here.

Greetings
Tobias

Hey @nmtoblum, thanks for reporting this! Thanks for linking to your test project, I will see if I can repro on my machine and get back to you ASAP.

One thing I noticed, which we have done a poor job at documenting...@fluidframework/azure-client version 1.1 or greater have an issue right now because of a breaking change they introduced that we only recently caught. If you are testing in Teams, that would probably line up with the AzureUser issue you were seeing.

We have a server-side patch coming next week, but in the meantime if you change the @fluidframework/azure-client version to "~1.0.2" that should at least fix that issue.

As for this one:

And I even went back one more step and tried the same as vanilla live-share:
https://github.com/toblum/spotalot/tree/minimal

Here I always get the error: Error: Registry does not contain entry for the package

This is probably because you were testing in the same meeting as before. This is something Live Share Turbo / React are meant to help. Because the container was created using the Live Share Turbo structure, that means the schema you defined for the base client mismatched that of what is used in Turbo/React. The base Live Share package doesn't yet support migrating schema, but to fix you can just create a new meeting and test from there.

Let me know if this helps!

Also this isn't the issue, but I noticed the following:

const { created } = useLiveShareContext();
const host = TestLiveShareHost.create();
return (
		<div>
			SidePanel
			<LiveShareProvider joinOnLoad host={host}>
				LSP
				{created}
				<GameSettings></GameSettings>
			</LiveShareProvider>
		</div>
);

First issue, useLiveShareContext(); should only be accessed from within a child component. I would nest another component within SidePanel.tsx, because otherwise that will always be undefined. Also, created is referring to whether the local user was the one to create the container.

The second is regarding the host. Generally, you should either wrap those in a useState or even define it outside of the component. This will ensure it doesn't re-create the object on every render.

I would recommend something closer to the following:

import { LiveShareProvider, useFluidObjectsContext } from "@microsoft/live-share-react";
import { LiveShareHost } from "@microsoft/live-share";
import { useState } from "react";

const SidePanel = () => {
  // teams-js must be initialize before LiveShareHost.create() will work
  const [host] = useState(LiveShareHost.create());
  return (
    <LiveShareProvider host={host} joinOnLoad>
      <SidePanelRenderer />
    </LiveShareProvider>
  )
}

const SidePanelRenderer = () => {
  const { container } = useFluidObjectsContext();
  if (!container) return (<Spinner />);
  return (<GameSettings />);
}

Hi Ryan,

thanks for your quick reply and for taking the time to investigate my problem.

The note about the version of @fluidframework/azure-client was spot on. That solved a lot of things already:

  • minimal: The error Registry does not contain entry for the package is gone and I think I could build a vanilla live-share solution now. I have not tested further yet.
  • live-share-turbo: This solution works now too.
  • live-share-react: With your code example it works now too. I probably oversimplified the code a bit during my tests. :-)

At first, however, no variant worked and the error messages were confusing, but I drilled a little deeper and I think I understood why my tests over the last few days gave such confusing results:

I always tested in teams meetings. Today, I tested the "minimal" example first and tried it successfully (that registered an exampleMap in the Fluid container). Then I tried the live-share-turbo solution in the same meeting. That then failed with an error message that looked like this:
image

Then I tried live-share-react example and got this error message:
image

The error happens here:
image

I made sure that the container exists before, so I couldn't explain why it's undefined. After logging the container I saw it:
container is available, initial objects: {exampleMap: SharedMap}

The container already existed in the meeting (with the exampleMap from the minimal example I started earlier). So the live-share-turbo client in live-share-react took this existing container, but could not create it's internal TURBO_STATE_MAP object, but assumes that it's already there then breaks with the error above.

So I asked myself how this situation could be handled in Teams where maybe more then one live-share-* app is started in the same meeting. The first will win? But how should the second app handle this?

It would be nice if you could give me some guidance on this. Many thanks in advance for your support.

Many greetings
Tobias

Hey @nmtoblum, yeah this is great feedback, thank you for being so thorough! We have some work to do in this area for sure. While we are formally releasing @microsoft/live-share, -media, and -canvas to v1.0.0 at Build in a couple weeks, the React & Turbo packages will stay in Preview. So we're still working some of this out, but I will make a note to ensure that the error messages be more consistent across these three paradigms.

What you are touching on here relates to how Fluid Framework works. Basically, anything that is in initialObjects from your ContainerSchema is created before the IFluidContainer is created, and currently they don't support changing those initialObjects after the container is created.

So I asked myself how this situation could be handled in Teams where maybe more then one live-share-* app is started in the same meeting. The first will win? But how should the second app handle this?

These containers are stored in our server for about 6 hours after you first install the app to a meeting, before we delete them. Each unique application in Teams gets one container per meeting, meaning that apps do not get access to any other container you have already created.

This is determined by two things, which we do primarily for security reasons and to avoid the issue which you mentioned above:

  1. Your application's unique id, as determined in your app manifest
  2. The origin URL of your application

That means that even if an app has the same identifier as you, if your origin is different then you will have two separate containers for a single meeting. You won't have any conflicts with other Teams apps, just your own while you are testing a different app schema within the same 6 hour window.

This gives you two ways to "reset" your container while you're still flushing out your app & schema:

  1. Simply create a new meeting and sideload it there. Meet Now meetings are the easiest way for this, generally.
  2. Change one character of your "id" in your app manifest and reinstall. Obviously you want a static app ID for your production app, but there is nothing stopping you from changing it as many times as you'd like during development. Usually we just do a new meeting though, since zipping the manifest and uploading the new app package is a bit clunky.

RE: other stuff mentioned

Containers created by LiveShareTurboClient (and consequentially live-share-react) are not compatible with that from the regular LiveShareClient. This is because Live Share Turbo actually abstracts away the schema so you don't have this issue. LiveShareTurboClient uses a single initialObjects entry called DynamicObjectsManager, which is what you're seeing in that first error message. It is the same as if you did this in the Live Share client:

import { LiveShareClient, TestLiveShareHost } from "@microsoft/live-share";
import { DynamicObjectsManager } from "@microsoft/live-share-turbo";

const schema = {
  initialObjects: {
    TURBO_STATE_MAP: DynamicObjectsManager,
  },
  dynamicObjectTypes: [...every DDS possible in your app],
}

We might change this in a future update -- we are talking with the Fluid Framework team about how we could create the TURBO_STATE_MAP without depending on initialObjects at all (effectively doing what they do under the hood w/ initialObjects, rather than adding an additional abstraction). However, it does mean that you can get some portability between live-share-react and live-share-turbo, as long as you are using the same keys for each DDS.

For example, if you use this in React:

import { useLiveState } from "@microsoft/live-share-react";

const FOO_KEY= "foo";
export const SomeComponent = () => {
  const [foo, setFoo] = useLiveState<string>(FOO_KEY, "bar");
  return (
    <div>
      {foo}
    </div>
  );
}

That is the same as this in live-share-turbo:

import { LiveShareTurboClient } from "@microsoft/live-share-turbo";
import { LiveState, TestLiveShareHost } from "@microsoft/live-share";

const FOO_KEY= "foo";

const host = TestLiveShareHost.create();
const client = new LiveShareTurboClient(host);
await client.join();

const fooState = await client.getDDS(FOO_KEY, LiveState);
fooState.on("stateChanged", (latest) => {
  // update app
});
await fooState.initialize("bar");

Technically you could make the LiveShareClient work with Turbo client as well, if you were really motivated, though at that point you might as well just use LiveShareTurboClient.

Hope this helps! Happy to answer more questions, and definitely keep sharing candid feedback, it's super helpful!

Hello Ryan,

thank you for all the information. It all helps me a lot to understand how it works. I'm also glad to hear that (vanilla) live-share will be 1.0 soon. Then with that we can build solutions that can be used productively later on.

Thanks also for the information on how new containers are created. If a new container is created per application ID and meeting, then no problems are to be expected and the observed irregularities can only be attributed to my unsuitable development setup. Now I simply create a new meeting more often. That makes it less complicated.

Otherwise, as soon as possible, I will just work a bit more with live-share(-react) and also onboard my colleagues. I have a few ideas for apps. If I run into any problems or questions along the way, I won't hesitate to post them again. Thanks again for the super helpful support.

Many greetings
Tobias