@microsoft/live-share-react 1.0.0-preview.9 → 1.0.1-preview.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/README.md +99 -35
  2. package/bin/index.d.ts +6 -6
  3. package/bin/index.js +6 -6
  4. package/bin/interfaces/LiveInterfaceExtensions.d.ts +25 -8
  5. package/bin/interfaces/LiveInterfaceExtensions.d.ts.map +1 -1
  6. package/bin/interfaces/LiveInterfaceExtensions.js +5 -5
  7. package/bin/interfaces/index.d.ts +1 -1
  8. package/bin/interfaces/index.js +1 -1
  9. package/bin/internal/errors.d.ts +26 -0
  10. package/bin/internal/errors.d.ts.map +1 -0
  11. package/bin/internal/errors.js +39 -0
  12. package/bin/internal/errors.js.map +1 -0
  13. package/bin/internal/index.d.ts +4 -0
  14. package/bin/internal/index.d.ts.map +1 -0
  15. package/bin/internal/index.js +4 -0
  16. package/bin/internal/index.js.map +1 -0
  17. package/bin/internal/type-guards.d.ts +6 -0
  18. package/bin/internal/type-guards.d.ts.map +1 -0
  19. package/bin/internal/type-guards.js +8 -0
  20. package/bin/internal/type-guards.js.map +1 -0
  21. package/bin/{internal-hooks → internal}/useSharedStateRegistry.d.ts +30 -30
  22. package/bin/internal/useSharedStateRegistry.d.ts.map +1 -0
  23. package/bin/{internal-hooks → internal}/useSharedStateRegistry.js +101 -99
  24. package/bin/internal/useSharedStateRegistry.js.map +1 -0
  25. package/bin/live-hooks/index.d.ts +6 -6
  26. package/bin/live-hooks/index.js +6 -6
  27. package/bin/live-hooks/useLiveCanvas.d.ts +27 -27
  28. package/bin/live-hooks/useLiveCanvas.d.ts.map +1 -1
  29. package/bin/live-hooks/useLiveCanvas.js +158 -166
  30. package/bin/live-hooks/useLiveCanvas.js.map +1 -1
  31. package/bin/live-hooks/useLiveEvent.d.ts +21 -21
  32. package/bin/live-hooks/useLiveEvent.d.ts.map +1 -1
  33. package/bin/live-hooks/useLiveEvent.js +100 -85
  34. package/bin/live-hooks/useLiveEvent.js.map +1 -1
  35. package/bin/live-hooks/useLivePresence.d.ts +25 -25
  36. package/bin/live-hooks/useLivePresence.d.ts.map +1 -1
  37. package/bin/live-hooks/useLivePresence.js +106 -92
  38. package/bin/live-hooks/useLivePresence.js.map +1 -1
  39. package/bin/live-hooks/useLiveState.d.ts +19 -19
  40. package/bin/live-hooks/useLiveState.d.ts.map +1 -1
  41. package/bin/live-hooks/useLiveState.js +79 -70
  42. package/bin/live-hooks/useLiveState.js.map +1 -1
  43. package/bin/live-hooks/useLiveTimer.d.ts +7 -7
  44. package/bin/live-hooks/useLiveTimer.d.ts.map +1 -1
  45. package/bin/live-hooks/useLiveTimer.js +150 -131
  46. package/bin/live-hooks/useLiveTimer.js.map +1 -1
  47. package/bin/live-hooks/useMediaSynchronizer.d.ts +22 -22
  48. package/bin/live-hooks/useMediaSynchronizer.d.ts.map +1 -1
  49. package/bin/live-hooks/useMediaSynchronizer.js +206 -161
  50. package/bin/live-hooks/useMediaSynchronizer.js.map +1 -1
  51. package/bin/providers/AzureProvider.d.ts +33 -33
  52. package/bin/providers/AzureProvider.d.ts.map +1 -1
  53. package/bin/providers/AzureProvider.js +101 -101
  54. package/bin/providers/AzureProvider.js.map +1 -1
  55. package/bin/providers/LiveShareProvider.d.ts +62 -41
  56. package/bin/providers/LiveShareProvider.d.ts.map +1 -1
  57. package/bin/providers/LiveShareProvider.js +68 -65
  58. package/bin/providers/LiveShareProvider.js.map +1 -1
  59. package/bin/providers/index.d.ts +2 -2
  60. package/bin/providers/index.js +2 -2
  61. package/bin/shared-hooks/index.d.ts +3 -4
  62. package/bin/shared-hooks/index.d.ts.map +1 -1
  63. package/bin/shared-hooks/index.js +3 -4
  64. package/bin/shared-hooks/index.js.map +1 -1
  65. package/bin/shared-hooks/useDynamicDDS.d.ts +18 -18
  66. package/bin/shared-hooks/useDynamicDDS.d.ts.map +1 -1
  67. package/bin/shared-hooks/useDynamicDDS.js +67 -67
  68. package/bin/shared-hooks/useDynamicDDS.js.map +1 -1
  69. package/bin/shared-hooks/useSharedMap.d.ts +21 -21
  70. package/bin/shared-hooks/useSharedMap.d.ts.map +1 -1
  71. package/bin/shared-hooks/useSharedMap.js +112 -105
  72. package/bin/shared-hooks/useSharedMap.js.map +1 -1
  73. package/bin/shared-hooks/useSharedState.d.ts +20 -20
  74. package/bin/shared-hooks/useSharedState.d.ts.map +1 -1
  75. package/bin/shared-hooks/useSharedState.js +72 -62
  76. package/bin/shared-hooks/useSharedState.js.map +1 -1
  77. package/bin/types/ActionTypes.d.ts +111 -29
  78. package/bin/types/ActionTypes.d.ts.map +1 -1
  79. package/bin/types/ActionTypes.js +5 -5
  80. package/bin/types/PropTypes.d.ts +7 -7
  81. package/bin/types/PropTypes.d.ts.map +1 -1
  82. package/bin/types/PropTypes.js +5 -5
  83. package/bin/types/ResultTypes.d.ts +174 -162
  84. package/bin/types/ResultTypes.d.ts.map +1 -1
  85. package/bin/types/ResultTypes.js +5 -5
  86. package/bin/types/index.d.ts +3 -3
  87. package/bin/types/index.js +3 -3
  88. package/bin/utils/index.d.ts +1 -1
  89. package/bin/utils/index.js +1 -1
  90. package/bin/utils/type-check.d.ts +14 -14
  91. package/bin/utils/type-check.js +53 -53
  92. package/package.json +18 -14
  93. package/src/interfaces/LiveInterfaceExtensions.ts +19 -2
  94. package/src/internal/errors.ts +48 -0
  95. package/src/internal/index.ts +3 -0
  96. package/src/internal/type-guards.ts +9 -0
  97. package/src/{internal-hooks → internal}/useSharedStateRegistry.ts +6 -5
  98. package/src/live-hooks/useLiveCanvas.ts +12 -20
  99. package/src/live-hooks/useLiveEvent.ts +41 -22
  100. package/src/live-hooks/useLivePresence.ts +52 -33
  101. package/src/live-hooks/useLiveState.ts +48 -53
  102. package/src/live-hooks/useLiveTimer.ts +50 -41
  103. package/src/live-hooks/useMediaSynchronizer.ts +108 -41
  104. package/src/providers/AzureProvider.tsx +3 -8
  105. package/src/providers/LiveShareProvider.tsx +46 -16
  106. package/src/shared-hooks/index.ts +0 -1
  107. package/src/shared-hooks/useDynamicDDS.ts +15 -8
  108. package/src/shared-hooks/useSharedMap.ts +48 -22
  109. package/src/shared-hooks/useSharedState.ts +15 -5
  110. package/src/types/ActionTypes.ts +100 -15
  111. package/src/types/ResultTypes.ts +23 -9
  112. package/bin/internal-hooks/index.d.ts +0 -2
  113. package/bin/internal-hooks/index.d.ts.map +0 -1
  114. package/bin/internal-hooks/index.js +0 -2
  115. package/bin/internal-hooks/index.js.map +0 -1
  116. package/bin/internal-hooks/useDynamicDDSRegistry.d.ts +0 -14
  117. package/bin/internal-hooks/useDynamicDDSRegistry.d.ts.map +0 -1
  118. package/bin/internal-hooks/useDynamicDDSRegistry.js +0 -142
  119. package/bin/internal-hooks/useDynamicDDSRegistry.js.map +0 -1
  120. package/bin/internal-hooks/useSharedStateRegistry.d.ts.map +0 -1
  121. package/bin/internal-hooks/useSharedStateRegistry.js.map +0 -1
  122. package/bin/internal-hooks/useStateRef.d.ts +0 -7
  123. package/bin/internal-hooks/useStateRef.d.ts.map +0 -1
  124. package/bin/internal-hooks/useStateRef.js +0 -15
  125. package/bin/internal-hooks/useStateRef.js.map +0 -1
  126. package/bin/live-hooks/useLiveAICompletion.d.ts +0 -29
  127. package/bin/live-hooks/useLiveAICompletion.d.ts.map +0 -1
  128. package/bin/live-hooks/useLiveAICompletion.js +0 -186
  129. package/bin/live-hooks/useLiveAICompletion.js.map +0 -1
  130. package/bin/live-hooks/useLiveCoPilot.d.ts +0 -28
  131. package/bin/live-hooks/useLiveCoPilot.d.ts.map +0 -1
  132. package/bin/live-hooks/useLiveCoPilot.js +0 -178
  133. package/bin/live-hooks/useLiveCoPilot.js.map +0 -1
  134. package/bin/live-share-turbo/AzureTurboClient.d.ts +0 -39
  135. package/bin/live-share-turbo/AzureTurboClient.d.ts.map +0 -1
  136. package/bin/live-share-turbo/AzureTurboClient.js +0 -56
  137. package/bin/live-share-turbo/AzureTurboClient.js.map +0 -1
  138. package/bin/live-share-turbo/LiveShareTurboClient.d.ts +0 -53
  139. package/bin/live-share-turbo/LiveShareTurboClient.d.ts.map +0 -1
  140. package/bin/live-share-turbo/LiveShareTurboClient.js +0 -69
  141. package/bin/live-share-turbo/LiveShareTurboClient.js.map +0 -1
  142. package/bin/live-share-turbo/dds-objects/TurboDataObject.d.ts +0 -31
  143. package/bin/live-share-turbo/dds-objects/TurboDataObject.d.ts.map +0 -1
  144. package/bin/live-share-turbo/dds-objects/TurboDataObject.js +0 -75
  145. package/bin/live-share-turbo/dds-objects/TurboDataObject.js.map +0 -1
  146. package/bin/live-share-turbo/dds-objects/TurboLiveCanvas.d.ts +0 -48
  147. package/bin/live-share-turbo/dds-objects/TurboLiveCanvas.d.ts.map +0 -1
  148. package/bin/live-share-turbo/dds-objects/TurboLiveCanvas.js +0 -104
  149. package/bin/live-share-turbo/dds-objects/TurboLiveCanvas.js.map +0 -1
  150. package/bin/live-share-turbo/dds-objects/TurboLiveEvent.d.ts +0 -28
  151. package/bin/live-share-turbo/dds-objects/TurboLiveEvent.d.ts.map +0 -1
  152. package/bin/live-share-turbo/dds-objects/TurboLiveEvent.js +0 -68
  153. package/bin/live-share-turbo/dds-objects/TurboLiveEvent.js.map +0 -1
  154. package/bin/live-share-turbo/dds-objects/TurboLiveMediaSession.d.ts +0 -60
  155. package/bin/live-share-turbo/dds-objects/TurboLiveMediaSession.d.ts.map +0 -1
  156. package/bin/live-share-turbo/dds-objects/TurboLiveMediaSession.js +0 -111
  157. package/bin/live-share-turbo/dds-objects/TurboLiveMediaSession.js.map +0 -1
  158. package/bin/live-share-turbo/dds-objects/TurboLivePresence.d.ts +0 -77
  159. package/bin/live-share-turbo/dds-objects/TurboLivePresence.d.ts.map +0 -1
  160. package/bin/live-share-turbo/dds-objects/TurboLivePresence.js +0 -137
  161. package/bin/live-share-turbo/dds-objects/TurboLivePresence.js.map +0 -1
  162. package/bin/live-share-turbo/dds-objects/TurboLiveState.d.ts +0 -30
  163. package/bin/live-share-turbo/dds-objects/TurboLiveState.d.ts.map +0 -1
  164. package/bin/live-share-turbo/dds-objects/TurboLiveState.js +0 -74
  165. package/bin/live-share-turbo/dds-objects/TurboLiveState.js.map +0 -1
  166. package/bin/live-share-turbo/dds-objects/TurboSharedMap.d.ts +0 -61
  167. package/bin/live-share-turbo/dds-objects/TurboSharedMap.d.ts.map +0 -1
  168. package/bin/live-share-turbo/dds-objects/TurboSharedMap.js +0 -108
  169. package/bin/live-share-turbo/dds-objects/TurboSharedMap.js.map +0 -1
  170. package/bin/live-share-turbo/dds-objects/index.d.ts +0 -8
  171. package/bin/live-share-turbo/dds-objects/index.d.ts.map +0 -1
  172. package/bin/live-share-turbo/dds-objects/index.js +0 -8
  173. package/bin/live-share-turbo/dds-objects/index.js.map +0 -1
  174. package/bin/live-share-turbo/index.d.ts +0 -5
  175. package/bin/live-share-turbo/index.d.ts.map +0 -1
  176. package/bin/live-share-turbo/index.js +0 -5
  177. package/bin/live-share-turbo/index.js.map +0 -1
  178. package/bin/live-share-turbo/interfaces/IFluidTurboClient.d.ts +0 -15
  179. package/bin/live-share-turbo/interfaces/IFluidTurboClient.d.ts.map +0 -1
  180. package/bin/live-share-turbo/interfaces/IFluidTurboClient.js +0 -2
  181. package/bin/live-share-turbo/interfaces/IFluidTurboClient.js.map +0 -1
  182. package/bin/live-share-turbo/interfaces/index.d.ts +0 -3
  183. package/bin/live-share-turbo/interfaces/index.d.ts.map +0 -1
  184. package/bin/live-share-turbo/interfaces/index.js +0 -3
  185. package/bin/live-share-turbo/interfaces/index.js.map +0 -1
  186. package/bin/live-share-turbo/internals/FluidTurboClient.d.ts +0 -39
  187. package/bin/live-share-turbo/internals/FluidTurboClient.d.ts.map +0 -1
  188. package/bin/live-share-turbo/internals/FluidTurboClient.js +0 -121
  189. package/bin/live-share-turbo/internals/FluidTurboClient.js.map +0 -1
  190. package/bin/live-share-turbo/internals/index.d.ts +0 -2
  191. package/bin/live-share-turbo/internals/index.d.ts.map +0 -1
  192. package/bin/live-share-turbo/internals/index.js +0 -2
  193. package/bin/live-share-turbo/internals/index.js.map +0 -1
  194. package/bin/live-share-turbo/internals/types.d.ts +0 -4
  195. package/bin/live-share-turbo/internals/types.d.ts.map +0 -1
  196. package/bin/live-share-turbo/internals/types.js +0 -2
  197. package/bin/live-share-turbo/internals/types.js.map +0 -1
  198. package/bin/providers/FluidContextProvider.d.ts +0 -34
  199. package/bin/providers/FluidContextProvider.d.ts.map +0 -1
  200. package/bin/providers/FluidContextProvider.js +0 -99
  201. package/bin/providers/FluidContextProvider.js.map +0 -1
  202. package/bin/providers/LiveShareContextProvider.d.ts +0 -42
  203. package/bin/providers/LiveShareContextProvider.d.ts.map +0 -1
  204. package/bin/providers/LiveShareContextProvider.js +0 -63
  205. package/bin/providers/LiveShareContextProvider.js.map +0 -1
  206. package/bin/shared-hooks/useLockedTask.d.ts +0 -11
  207. package/bin/shared-hooks/useLockedTask.d.ts.map +0 -1
  208. package/bin/shared-hooks/useLockedTask.js +0 -82
  209. package/bin/shared-hooks/useLockedTask.js.map +0 -1
  210. package/bin/shared-hooks/useTaskManager.d.ts +0 -13
  211. package/bin/shared-hooks/useTaskManager.d.ts.map +0 -1
  212. package/bin/shared-hooks/useTaskManager.js +0 -86
  213. package/bin/shared-hooks/useTaskManager.js.map +0 -1
  214. package/bin/utils/getContainerSchema.d.ts +0 -8
  215. package/bin/utils/getContainerSchema.d.ts.map +0 -1
  216. package/bin/utils/getContainerSchema.js +0 -41
  217. package/bin/utils/getContainerSchema.js.map +0 -1
  218. package/src/internal-hooks/index.ts +0 -1
  219. package/src/shared-hooks/useTaskManager.ts +0 -82
  220. package/tinylicious.log +0 -4
@@ -7,14 +7,13 @@ import { LiveState, UserMeetingRole } from "@microsoft/live-share";
7
7
  import React from "react";
8
8
  import { SetLiveStateAction } from "../types";
9
9
  import { useDynamicDDS } from "../shared-hooks";
10
-
11
- interface ILiveStateStatus<
12
- TState extends string = string,
13
- TData extends object = object
14
- > {
15
- state?: TState;
16
- data?: TData;
17
- }
10
+ import { useFluidObjectsContext } from "../providers";
11
+ import {
12
+ ActionContainerNotJoinedError,
13
+ ActionLiveDataObjectInitializedError,
14
+ ActionLiveDataObjectUndefinedError,
15
+ isPrevStateCallback,
16
+ } from "../internal";
18
17
 
19
18
  /**
20
19
  * React hook for using a Live Share `LiveState`.
@@ -24,57 +23,58 @@ interface ILiveStateStatus<
24
23
  *
25
24
  * @param uniqueKey the unique key for the `LiveEvent`. If one does not yet exist, a new one will be created, otherwise it will use the existing one.
26
25
  * @param initialState Optional. the initial state value of type TState
27
- * @param initialData Optional. the initial data value of type TData
28
26
  * @param allowedRoles Optional. the user roles that are allowed to mutate the synchronized state
29
- * @returns ordered values: first value is the synchronized state value, second is synchronized data value, and third is a setter to change the state/data values.
27
+ * @returns ordered values: first value is the synchronized state value and the second is a setter to change the state value.
28
+ * The setter returns a void promise, which will throw if the user does not have the required roles to set.
30
29
  */
31
- export function useLiveState<
32
- TState extends string = string,
33
- TData extends object = object
34
- >(
30
+ export function useLiveState<TState = any>(
35
31
  uniqueKey: string,
36
- initialState?: TState,
37
- initialData?: TData,
32
+ initialState: TState,
38
33
  allowedRoles?: UserMeetingRole[]
39
- ): [TState | undefined, TData | undefined, SetLiveStateAction<TState, TData>] {
40
- const [current, setCurrent] = React.useState<
41
- ILiveStateStatus<TState, TData>
42
- >({
43
- state: initialState,
44
- data: initialData,
45
- });
34
+ ): [TState, SetLiveStateAction<TState>, LiveState<TState> | undefined] {
35
+ const [currentState, setCurrentState] =
36
+ React.useState<TState>(initialState);
46
37
  /**
47
38
  * User facing: dynamically load the DDS for the given unique key.
48
39
  */
49
- const { dds: liveState } = useDynamicDDS<LiveState<TData>>(
40
+ const { dds: liveState } = useDynamicDDS<LiveState<TState>>(
50
41
  uniqueKey,
51
- LiveState<TData>
42
+ LiveState<TState>
52
43
  );
53
44
 
45
+ const { container } = useFluidObjectsContext();
46
+
54
47
  /**
55
48
  * Change state callback that is user facing
49
+ * @param state TState to set
50
+ * @returns void promise, which will throw if the user does not have the required roles
56
51
  */
57
- const changeState = React.useCallback(
58
- (state: TState, value?: TData | undefined) => {
59
- if (!liveState) {
60
- console.error(
61
- new Error(
62
- "Cannot call changeState when liveState is undefined"
63
- )
52
+ const setState = React.useCallback(
53
+ async (state: TState | ((prevState: TState) => TState)) => {
54
+ if (!container) {
55
+ throw new ActionContainerNotJoinedError(
56
+ "liveState",
57
+ "setState"
58
+ );
59
+ }
60
+ if (liveState === undefined) {
61
+ throw new ActionLiveDataObjectUndefinedError(
62
+ "liveState",
63
+ "setState"
64
64
  );
65
- return;
66
65
  }
67
66
  if (!liveState.isInitialized) {
68
- console.error(
69
- new Error(
70
- "Cannot call changeState while liveState is not started"
71
- )
67
+ throw new ActionLiveDataObjectInitializedError(
68
+ "liveState",
69
+ "setState"
72
70
  );
73
- return;
74
71
  }
75
- liveState?.changeState(state, value);
72
+ const valueToSet = isPrevStateCallback<TState>(state)
73
+ ? state(liveState.state)
74
+ : state;
75
+ return await liveState.set(valueToSet);
76
76
  },
77
- [liveState]
77
+ [container, liveState]
78
78
  );
79
79
 
80
80
  /**
@@ -83,26 +83,21 @@ export function useLiveState<
83
83
  React.useEffect(() => {
84
84
  if (liveState === undefined) return;
85
85
 
86
- const onStateChanged = (state: TState, data: TData | undefined) => {
87
- setCurrent({
88
- state,
89
- data,
90
- });
86
+ const onStateChanged = (state: TState) => {
87
+ setCurrentState(state);
91
88
  };
92
89
  liveState.on("stateChanged", onStateChanged);
93
90
  if (!liveState.isInitialized) {
94
- liveState.initialize(allowedRoles, initialState, initialData);
95
- if (liveState.state) {
96
- onStateChanged(liveState.state as TState, liveState.data);
97
- }
98
- } else if (liveState.state) {
99
- onStateChanged(liveState.state as TState, liveState.data);
91
+ liveState.initialize(initialState, allowedRoles);
92
+ }
93
+ if (JSON.stringify(liveState.state) !== JSON.stringify(initialState)) {
94
+ onStateChanged(liveState.state);
100
95
  }
101
96
 
102
97
  return () => {
103
98
  liveState?.off("stateChanged", onStateChanged);
104
99
  };
105
- }, [liveState?.isInitialized]);
100
+ }, [liveState]);
106
101
 
107
- return [current?.state, current?.data, changeState];
102
+ return [currentState, setState, liveState];
108
103
  }
@@ -22,6 +22,12 @@ import {
22
22
  OnTimerTickAction,
23
23
  } from "../types";
24
24
  import { useDynamicDDS } from "../shared-hooks";
25
+ import { useFluidObjectsContext } from "../providers";
26
+ import {
27
+ ActionContainerNotJoinedError,
28
+ ActionLiveDataObjectInitializedError,
29
+ ActionLiveDataObjectUndefinedError,
30
+ } from "../internal";
25
31
 
26
32
  export function useLiveTimer(
27
33
  uniqueKey: string,
@@ -31,7 +37,7 @@ export function useLiveTimer(
31
37
  onStart?: OnTimerDidStartAction,
32
38
  onPause?: OnTimerDidPauseAction,
33
39
  onPlay?: OnTimerDidPlayAction,
34
- onFinish?: OnTimerDidFinishAction,
40
+ onFinish?: OnTimerDidFinishAction
35
41
  ): IUseLiveTimerResults {
36
42
  /**
37
43
  * User facing: Stateful timer config.
@@ -46,74 +52,77 @@ export function useLiveTimer(
46
52
  */
47
53
  const { dds: liveTimer } = useDynamicDDS<LiveTimer>(uniqueKey, LiveTimer);
48
54
 
55
+ const { container } = useFluidObjectsContext();
56
+
49
57
  /**
50
- * User facing: callback to send event through `LiveTimer`
58
+ * Callback to send event through `LiveTimer`
59
+ * @param duration the duration for the timer in milliseconds
60
+ * @returns void promise that will throw when user does not have required roles
51
61
  */
52
62
  const start: OnStartTimerAction = React.useCallback(
53
- (duration: number): void => {
54
- if (liveTimer?.isInitialized === undefined) {
55
- console.error(
56
- new Error("Cannot call start when liveTimer is undefined")
63
+ async (duration: number) => {
64
+ if (!container) {
65
+ throw new ActionContainerNotJoinedError("liveTimer", "start");
66
+ }
67
+ if (liveTimer === undefined) {
68
+ throw new ActionLiveDataObjectUndefinedError(
69
+ "liveTimer",
70
+ "start"
57
71
  );
58
- return;
59
72
  }
60
73
  if (!liveTimer.isInitialized) {
61
- console.error(
62
- new Error(
63
- "Cannot call start while liveTimer is not started"
64
- )
74
+ throw new ActionLiveDataObjectInitializedError(
75
+ "liveTimer",
76
+ "start"
65
77
  );
66
- return;
67
78
  }
68
- liveTimer?.start(duration);
79
+ return await liveTimer.start(duration);
69
80
  },
70
- [liveTimer?.isInitialized]
81
+ [container, liveTimer]
71
82
  );
72
83
 
73
84
  /**
74
- * User facing: callback to send event through `LiveTimer`
85
+ * Callback to send event through `LiveTimer`
86
+ * @returns void promise that will throw when user does not have required roles
75
87
  */
76
- const play: OnPlayTimerAction = React.useCallback((): void => {
77
- if (liveTimer?.isInitialized === undefined) {
78
- console.error(
79
- new Error("Cannot call play when liveTimer is undefined")
80
- );
81
- return;
88
+ const play: OnPlayTimerAction = React.useCallback(async () => {
89
+ if (!container) {
90
+ throw new ActionContainerNotJoinedError("liveTimer", "play");
91
+ }
92
+ if (liveTimer === undefined) {
93
+ throw new ActionLiveDataObjectUndefinedError("liveTimer", "play");
82
94
  }
83
95
  if (!liveTimer.isInitialized) {
84
- console.error(
85
- new Error("Cannot call play while liveTimer is not started")
86
- );
87
- return;
96
+ throw new ActionLiveDataObjectInitializedError("liveTimer", "play");
88
97
  }
89
- liveTimer?.play();
90
- }, [liveTimer?.isInitialized]);
98
+ return await liveTimer.play();
99
+ }, [container, liveTimer]);
91
100
 
92
101
  /**
93
- * User facing: callback to send event through `LiveTimer`
102
+ * Callback to send event through `LiveTimer`
103
+ * @returns void promise that will throw when user does not have required roles
94
104
  */
95
- const pause: OnPauseTimerAction = React.useCallback((): void => {
96
- if (liveTimer?.isInitialized === undefined) {
97
- console.error(
98
- new Error("Cannot call pause when liveTimer is undefined")
99
- );
100
- return;
105
+ const pause: OnPauseTimerAction = React.useCallback(async () => {
106
+ if (!container) {
107
+ throw new ActionContainerNotJoinedError("liveTimer", "pause");
108
+ }
109
+ if (liveTimer === undefined) {
110
+ throw new ActionLiveDataObjectUndefinedError("liveTimer", "pause");
101
111
  }
102
112
  if (!liveTimer.isInitialized) {
103
- console.error(
104
- new Error("Cannot call pause while liveTimer is not started")
113
+ throw new ActionLiveDataObjectInitializedError(
114
+ "liveTimer",
115
+ "pause"
105
116
  );
106
- return;
107
117
  }
108
- liveTimer?.pause();
109
- }, [liveTimer?.isInitialized]);
118
+ return await liveTimer.pause();
119
+ }, [container, liveTimer]);
110
120
 
111
121
  /**
112
122
  * Setup change listeners and start `LiveTimer` if needed
113
123
  */
114
124
  React.useEffect(() => {
115
- if (liveTimer === undefined)
116
- return;
125
+ if (liveTimer === undefined) return;
117
126
  // Register event listeners
118
127
  const onTimerConfigChange = (config: ITimerConfig) => {
119
128
  setTimerConfig(config);
@@ -16,6 +16,11 @@ import {
16
16
  import { isExtendedMediaMetadata, isMediaElement, isRefObject } from "../utils";
17
17
  import { useDynamicDDS } from "../shared-hooks";
18
18
  import { IUseMediaSynchronizerResults } from "../types";
19
+ import { useFluidObjectsContext } from "../providers";
20
+ import {
21
+ ActionContainerNotJoinedError,
22
+ ActionLiveDataObjectUndefinedError,
23
+ } from "../internal";
19
24
 
20
25
  /**
21
26
  * React hook for using a Live Share media `MediaPlayerSynchronizer`.
@@ -24,7 +29,7 @@ import { IUseMediaSynchronizerResults } from "../types";
24
29
  * Use this hook if you want to synchronize the playback position of a video or audio element during a Live Share session.
25
30
  *
26
31
  * @param uniqueKey uniqueKey value for the `LiveMediaSession` DDS object used by `MediaPlayerSynchronizer`.
27
- * @param mediaPlayerElementRef React RefObject containing object/element conforming to IMediaPlayer interface or string id for <video> / <audio> element.
32
+ * @param mediaPlayerElement React RefObject containing object/element conforming to IMediaPlayer interface, `IMediaPlayer` object, or string id for <video> / <audio> element.
28
33
  * @param initialTrack initial track to load. Either ExtendedMediaMetadata, trackId string, or null.
29
34
  * @param allowedRoles Optional. Array of user roles that are eligible to modify group playback state.
30
35
  * @param viewOnly Optional. Flag for whether or not the media synchronizer should be in viewOnly mode.
@@ -32,7 +37,11 @@ import { IUseMediaSynchronizerResults } from "../types";
32
37
  */
33
38
  export function useMediaSynchronizer(
34
39
  uniqueKey: string,
35
- mediaPlayerElementRef: React.RefObject<IMediaPlayer> | string,
40
+ mediaPlayerElement:
41
+ | React.RefObject<IMediaPlayer>
42
+ | IMediaPlayer
43
+ | string
44
+ | null,
36
45
  initialTrack: Partial<ExtendedMediaMetadata> | string | null,
37
46
  allowedRoles?: UserMeetingRole[],
38
47
  viewOnly?: boolean
@@ -52,47 +61,99 @@ export function useMediaSynchronizer(
52
61
  /**
53
62
  * User facing: dynamically load the LiveMediaSession DDS for the given unique key.
54
63
  */
55
- const { dds: mediaSession } = useDynamicDDS<LiveMediaSession>(uniqueKey, LiveMediaSession);
64
+ const { dds: mediaSession } = useDynamicDDS<LiveMediaSession>(
65
+ uniqueKey,
66
+ LiveMediaSession
67
+ );
68
+
69
+ const { container } = useFluidObjectsContext();
56
70
 
57
71
  /**
58
- * User facing: play callback
72
+ * Play callback
59
73
  */
60
- const play = React.useCallback((): void => {
61
- mediaSynchronizer?.play();
62
- }, [mediaSynchronizer]);
74
+ const play = React.useCallback(async () => {
75
+ if (!container) {
76
+ throw new ActionContainerNotJoinedError(
77
+ "mediaSynchronizer",
78
+ "play"
79
+ );
80
+ }
81
+ if (mediaSynchronizer === undefined) {
82
+ throw new ActionLiveDataObjectUndefinedError(
83
+ "mediaSynchronizer",
84
+ "play"
85
+ );
86
+ }
87
+ return await mediaSynchronizer.play();
88
+ }, [container, mediaSynchronizer]);
63
89
 
64
90
  /**
65
- * User facing: pause callback
91
+ * Pause callback
66
92
  */
67
- const pause = React.useCallback((): void => {
68
- mediaSynchronizer?.pause();
69
- }, [mediaSynchronizer]);
93
+ const pause = React.useCallback(async () => {
94
+ if (!container) {
95
+ throw new ActionContainerNotJoinedError(
96
+ "mediaSynchronizer",
97
+ "pause"
98
+ );
99
+ }
100
+ if (mediaSynchronizer === undefined) {
101
+ throw new ActionLiveDataObjectUndefinedError(
102
+ "mediaSynchronizer",
103
+ "pause"
104
+ );
105
+ }
106
+ return await mediaSynchronizer.pause();
107
+ }, [container, mediaSynchronizer]);
70
108
 
71
109
  /**
72
110
  * User facing: seek callback
73
111
  */
74
112
  const seekTo = React.useCallback(
75
- (time: number): void => {
76
- mediaSynchronizer?.seekTo(time);
113
+ async (time: number) => {
114
+ if (!container) {
115
+ throw new ActionContainerNotJoinedError(
116
+ "mediaSynchronizer",
117
+ "seekTo"
118
+ );
119
+ }
120
+ if (mediaSynchronizer === undefined) {
121
+ throw new ActionLiveDataObjectUndefinedError(
122
+ "mediaSynchronizer",
123
+ "seekTo"
124
+ );
125
+ }
126
+ return await mediaSynchronizer.seekTo(time);
77
127
  },
78
- [mediaSynchronizer]
128
+ [container, mediaSynchronizer]
79
129
  );
80
130
 
81
131
  /**
82
132
  * User facing: set track callback
83
133
  */
84
134
  const setTrack = React.useCallback(
85
- (track: Partial<ExtendedMediaMetadata> | string | null): void => {
86
- // TODO: fix force unwrap once synchronizer uses correct types
135
+ async (track: Partial<ExtendedMediaMetadata> | string | null) => {
136
+ if (!container) {
137
+ throw new ActionContainerNotJoinedError(
138
+ "mediaSynchronizer",
139
+ "setTrack"
140
+ );
141
+ }
142
+ if (mediaSynchronizer === undefined) {
143
+ throw new ActionLiveDataObjectUndefinedError(
144
+ "mediaSynchronizer",
145
+ "setTrack"
146
+ );
147
+ }
87
148
  if (isExtendedMediaMetadata(track)) {
88
- mediaSynchronizer!.setTrack(track);
149
+ return await mediaSynchronizer.setTrack(track);
89
150
  } else if (typeof track === "string") {
90
- mediaSynchronizer!.setTrack({
151
+ return await mediaSynchronizer.setTrack({
91
152
  trackIdentifier: track,
92
153
  } as ExtendedMediaMetadata);
93
154
  }
94
155
  },
95
- [mediaSynchronizer]
156
+ [container, mediaSynchronizer]
96
157
  );
97
158
 
98
159
  /**
@@ -119,18 +180,20 @@ export function useMediaSynchronizer(
119
180
  * Setup change listeners and start `LiveMediaSession` if needed
120
181
  */
121
182
  React.useEffect(() => {
122
- if (mediaSession === undefined || !mediaPlayerElementRef)
123
- return;
183
+ if (mediaSession === undefined || !mediaPlayerElement) return;
184
+ let mounted = true;
124
185
  // Query the HTML5 media element from the document and set reference
125
186
  let mediaPlayer: IMediaPlayer | undefined;
126
- if (isRefObject<IMediaPlayer>(mediaPlayerElementRef)) {
127
- if (!mediaPlayerElementRef.current) return;
128
- mediaPlayer = mediaPlayerElementRef.current;
129
- } else {
130
- const mediaPlayerElement = document.getElementById(
131
- mediaPlayerElementRef
187
+ if (isRefObject<IMediaPlayer>(mediaPlayerElement)) {
188
+ if (!mediaPlayerElement.current) return;
189
+ mediaPlayer = mediaPlayerElement.current;
190
+ } else if (typeof mediaPlayerElement === "string") {
191
+ const mediaElement = document.getElementById(
192
+ mediaPlayerElement
132
193
  ) as any;
133
- if (!isMediaElement(mediaPlayerElement)) return;
194
+ if (!isMediaElement(mediaElement)) return;
195
+ mediaPlayer = mediaElement;
196
+ } else {
134
197
  mediaPlayer = mediaPlayerElement;
135
198
  }
136
199
  if (initialTrack) {
@@ -152,27 +215,31 @@ export function useMediaSynchronizer(
152
215
  // Start synchronizing the media session
153
216
  mediaSession.initialize(allowedRoles ?? []);
154
217
  } else if (initialTrack) {
155
- // If we have already started the media session, the synchronizer won't have initial track
156
- // data now that we are remounting.
157
- // TODO: if player is loaded twice in one app at once, then this has a chance to disrupt the
158
- // group state by resetting the track. MediaPlayerSynchronizer does not currently have a way to
159
- // get the current track, so to fix this we need a better solution.
160
- if (isExtendedMediaMetadata(initialTrack)) {
161
- synchronizer.setTrack(initialTrack);
162
- } else if (typeof initialTrack === "string") {
163
- synchronizer.setTrack({
164
- trackIdentifier: initialTrack,
165
- } as ExtendedMediaMetadata);
166
- }
218
+ mediaSession.onLocalUserAllowed(async () => {
219
+ if (!mounted) return;
220
+ try {
221
+ if (isExtendedMediaMetadata(initialTrack)) {
222
+ await synchronizer.setTrack(initialTrack);
223
+ } else if (typeof initialTrack === "string") {
224
+ await synchronizer.setTrack({
225
+ trackIdentifier: initialTrack,
226
+ } as ExtendedMediaMetadata);
227
+ }
228
+ } catch (err) {
229
+ console.error(err);
230
+ }
231
+ });
167
232
  }
168
233
  setMediaSynchronizer(synchronizer);
169
234
 
170
235
  return () => {
236
+ mounted = false;
171
237
  synchronizer.removeAllListeners();
172
238
  mediaSession.removeAllListeners();
173
239
  synchronizer?.end();
240
+ mediaSession?.dispose();
174
241
  };
175
- }, [mediaSession]);
242
+ }, [mediaSession, mediaPlayerElement]);
176
243
 
177
244
  /**
178
245
  * Change view in media synchronizer only if prop changes
@@ -3,10 +3,7 @@
3
3
  * Licensed under the Microsoft Live Share SDK License.
4
4
  */
5
5
 
6
- import {
7
- IFluidContainer,
8
- LoadableObjectClassRecord,
9
- } from "fluid-framework";
6
+ import { IFluidContainer, LoadableObjectClassRecord } from "fluid-framework";
10
7
  import React from "react";
11
8
  import {
12
9
  AzureClientProps,
@@ -16,7 +13,7 @@ import { IAzureContainerResults } from "../types";
16
13
  import {
17
14
  ISharedStateRegistryResponse,
18
15
  useSharedStateRegistry,
19
- } from "../internal-hooks";
16
+ } from "../internal";
20
17
  import {
21
18
  AzureTurboClient,
22
19
  IFluidTurboClient,
@@ -58,9 +55,7 @@ interface IAzureProviderProps {
58
55
  /**
59
56
  * React Context provider component for using Fluid data objects & joining/creating a Fluid document `AzureClient`.
60
57
  */
61
- export const AzureProvider: React.FC<IAzureProviderProps> = (
62
- props
63
- ) => {
58
+ export const AzureProvider: React.FC<IAzureProviderProps> = (props) => {
64
59
  const startedRef = React.useRef<boolean>(false);
65
60
  const clientRef = React.useRef<AzureTurboClient>(
66
61
  new AzureTurboClient(props.clientOptions)
@@ -5,18 +5,44 @@
5
5
 
6
6
  import { IFluidContainer, LoadableObjectClassRecord } from "fluid-framework";
7
7
  import React from "react";
8
- import { ILiveShareContainerResults } from "../types";
9
- import { useSharedStateRegistry } from "../internal-hooks";
10
- import { ILiveShareClientOptions, ILiveShareHost } from "@microsoft/live-share";
8
+ import { useSharedStateRegistry } from "../internal";
9
+ import {
10
+ ILiveShareClientOptions,
11
+ ILiveShareHost,
12
+ ILiveShareJoinResults,
13
+ ITimestampProvider,
14
+ } from "@microsoft/live-share";
11
15
  import { FluidContext } from "./AzureProvider";
12
16
  import { LiveShareTurboClient } from "@microsoft/live-share-turbo";
13
17
 
14
- interface ILiveShareContext {
15
- created: boolean | undefined;
18
+ export interface ILiveShareContext {
19
+ /**
20
+ * True if the local user created the Fluid container
21
+ */
22
+ created: boolean;
23
+ /**
24
+ * True if connected to the Live Share container
25
+ */
26
+ joined: boolean;
27
+ /**
28
+ * An error that will be defined if there was a problem joining the container, or undefined if not.
29
+ */
30
+ joinError: Error | undefined;
31
+ /**
32
+ * Live Share timestamp provider. Can be used to `.getTimestamp()` for a global clock value.
33
+ * This reference timestamp value should be fairly consistent for all users in the session.
34
+ */
35
+ timestampProvider: ITimestampProvider | undefined;
36
+ /**
37
+ * Join callback method
38
+ * @param initialObjects Optional. The initial objects for the Fluid container schema.
39
+ * @param onInitializeContainer Optional. Callback for when the container is first created.
40
+ * @returns `ILiveShareJoinResults`, which includes the Fluid container
41
+ */
16
42
  join: (
17
43
  initialObjects?: LoadableObjectClassRecord,
18
44
  onInitializeContainer?: (container: IFluidContainer) => void
19
- ) => Promise<ILiveShareContainerResults>;
45
+ ) => Promise<ILiveShareJoinResults>;
20
46
  }
21
47
 
22
48
  export const LiveShareContext = React.createContext<ILiveShareContext>(
@@ -54,15 +80,13 @@ interface ILiveShareProviderProps {
54
80
  /**
55
81
  * React Context provider component for using Live Share data objects & joining a Live Share session using `LiveShareClient`.
56
82
  */
57
- export const LiveShareProvider: React.FC<
58
- ILiveShareProviderProps
59
- > = (props) => {
83
+ export const LiveShareProvider: React.FC<ILiveShareProviderProps> = (props) => {
60
84
  const startedRef = React.useRef(false);
61
85
  const clientRef = React.useRef(
62
86
  new LiveShareTurboClient(props.host, props.clientOptions)
63
87
  );
64
88
  const [results, setResults] = React.useState<
65
- ILiveShareContainerResults | undefined
89
+ ILiveShareJoinResults | undefined
66
90
  >();
67
91
  const [joinError, setJoinError] = React.useState<Error | undefined>();
68
92
 
@@ -75,10 +99,12 @@ export const LiveShareProvider: React.FC<
75
99
  async (
76
100
  initialObjects?: LoadableObjectClassRecord,
77
101
  onInitializeContainer?: (container: IFluidContainer) => void
78
- ): Promise<ILiveShareContainerResults> => {
102
+ ): Promise<ILiveShareJoinResults> => {
79
103
  startedRef.current = true;
80
- const results: ILiveShareContainerResults =
81
- await clientRef.current.join(initialObjects, onInitializeContainer);
104
+ const results = await clientRef.current.join(
105
+ initialObjects,
106
+ onInitializeContainer
107
+ );
82
108
  setResults(results);
83
109
  return results;
84
110
  },
@@ -92,9 +118,10 @@ export const LiveShareProvider: React.FC<
92
118
  // This hook should only be called once, so we use a ref to track if it has been called.
93
119
  // This is a workaround for the fact that useEffect is called twice on initial render in React V18.
94
120
  // We are not doing this here for backwards compatibility. View the README for more information.
95
- if (results !== undefined || startedRef.current || !props.joinOnLoad) return;
121
+ if (results !== undefined || startedRef.current || !props.joinOnLoad)
122
+ return;
96
123
  join(props.initialObjects).catch((error) => {
97
- console.log(error);
124
+ console.error(error);
98
125
  if (error instanceof Error) {
99
126
  setJoinError(error);
100
127
  } else {
@@ -110,7 +137,10 @@ export const LiveShareProvider: React.FC<
110
137
  return (
111
138
  <LiveShareContext.Provider
112
139
  value={{
113
- created: results?.created,
140
+ created: !!results?.created,
141
+ timestampProvider: results?.timestampProvider,
142
+ joined: !!results?.container,
143
+ joinError,
114
144
  join,
115
145
  }}
116
146
  >
@@ -1,4 +1,3 @@
1
1
  export * from "./useSharedState";
2
2
  export * from "./useSharedMap";
3
3
  export * from "./useDynamicDDS";
4
- export * from "./useTaskManager";