internal void Initialize(IHostAppUser hostAppUser, Guid userId, Guid ephemeralUserId, MixedRealityExtensionApp app)
 {
     HostAppUser = hostAppUser;
     Name        = HostAppUser.Name;
     base.Initialize(userId, app);
     EphemeralUserId = ephemeralUserId;
 }
        private Guid GenerateObfuscatedUserId(IHostAppUser hostAppUser, string salt)
        {
            using (SHA256 hasher = SHA256.Create())
            {
                string hashString = $"{hostAppUser.HostUserId}:{salt}";

                var encoder  = new UTF8Encoding();
                var hashedId = Convert.ToBase64String(
                    hasher.ComputeHash(encoder.GetBytes(hashString))
                    );

                return(UtilMethodsGodot.StringToGuid(hashedId));
            }
        }
        /// <inheritdoc />
        public void UserJoin(Node userNode, IHostAppUser hostAppUser, bool isLocalUser)
        {
            void PerformUserJoin()
            {
                // only join the user if required
                if (isLocalUser &&
                    !GrantedPermissions.HasFlag(Permissions.UserInteraction) &&
                    !GrantedPermissions.HasFlag(Permissions.UserTracking))
                {
                    return;
                }

                var user = userNode.GetChildren <User>()
                           .FirstOrDefault(_user => _user.AppInstanceId == this.InstanceId);

                if (user == null)
                {
                    user = new User();
                    userNode.AddChild(user);

                    // Generate the obfuscated user ID based on user tracking permission.
                    Guid instancedUserId = GenerateObfuscatedUserId(hostAppUser, EphemeralAppId);
                    Guid userId          = instancedUserId;
                    if ((!isLocalUser || GrantedPermissions.HasFlag(Permissions.UserTracking)) && !string.IsNullOrEmpty(GlobalAppId))
                    {
                        userId = GenerateObfuscatedUserId(hostAppUser, GlobalAppId);
                    }

                    user.Initialize(hostAppUser, userId, instancedUserId, this);
                }

                // TODO @tombu - Wait for the app to send back a success for join?
                _userManager.AddUser(user);

                if (isLocalUser)
                {
                    Protocol.Send(new UserJoined()
                    {
                        User = new UserPatch(user)
                    });

                    LocalUser = user;

                    PhysicsBridge.LocalUserId = LocalUser.Id;

                    // Enable interactions for the user if given the UserInteraction permission.
                    if (GrantedPermissions.HasFlag(Permissions.UserInteraction))
                    {
                        EnableUserInteraction(user);
                    }
                }

                try
                {
                    OnUserJoined?.Invoke(user, isLocalUser);
                }
                catch (Exception e)
                {
                    GD.PushError(e.ToString());
                }
            }

            if (Protocol is Execution)
            {
                PerformUserJoin();
            }
            else
            {
                _executionProtocolActionQueue.Add(() => PerformUserJoin());
            }
        }