Skip to content

Conversation

@Kylo33
Copy link
Contributor

@Kylo33 Kylo33 commented Aug 9, 2025

To work with some Bluetooth smart cubes, it is required to know the cube's MAC address, which is not accessible through Web Bluetooth. For example, each of the GAN Gen2 smart cubes use the cube's MAC address to salt the encryption key and IV. While it is possible to get this through watchAdvertisements(), which gan-web-bluetooth and cstimer do, this doesn't work on all platforms (Linux and ChromeOS are not supported, and it requires enabling 'enable-experimental-web-platform-features' in Chrome flags). Both gan-web-bluetooth and cstimer have prompts to use if watchAdvertisements() doesn't work.

I was not sure how to implement something like this for cubing.js, but I took a stab at it in a way that I think aligns with some of the goals of the project. So, I added a parameter that is passed to each cube's connect function, so that cube configs that need the MAC address of the cube (and can't find it automatically) may choose to request it.

To prevent breaking any existing code, and to make it more simple for someone to get running, there is a default MAC address provider, which uses the browser's prompt() function, and some basic regex validation. I thought it would also be good to let developers give their own MAC address providers (i.e. to match their existing interfaces), so I added that as an option to BluetoothConnectOptions, and I wrapped the call to the MAC address provider with a function that throws an error if the string returned by the MAC address provider is invalid.

I tested that this code using make test-all, by manually confirming that the QiYi cube I have still functions correctly, and I verified that make build successfully builds the project.

I am not super confident with how I implemented the default MAC address provider, and I wasn't sure where to put some of the types (like ConnectionArgumentsand MacAddressProvider). Also, more generally, I would love to hear other ideas to solve this issue (maybe there's a better solution than passing a provider) or how I could make this change fit the goals of the project more!

To work with many Bluetooth smart cubes, it is required to know the
cube's MAC address, which is not accessible through Web Bluetooth. For
instance, each of the GAN Gen2 smart cubes use the cube's MAC address to
salt the encryption key and IV. While it is possible to get this through
`watchAdvertisements()`, this doesn't work on all platforms (Linux and
ChromeOS are not supported).

If applied, this commit adds a parameter that is passed to each cube's
connect function, so that cubes that need this information may choose to
request it. To keep this functionality simple, a request function can be
passed by a user calling the `connectSmartPuzzle()` function to, for
example, prompt the user for the cube's MAC address with a custom UI.
This commit also adds a simple default function which uses the browser's
default `prompt()`.
@lgarron
Copy link
Member

lgarron commented Aug 9, 2025

I think this is a good start, similar to what I would have done!

I have some thoughts I'll reply with later, but I think the main thing I would try to address is to make it more clear how/whether the implementation can be called as a fallback.

I think it's also worth providing two default implementations with an easy way to invoke them:

  • prompt() — simple, always works, but not great UX.
  • A modal <dialog> implementation.

Both can certainly implement a simple callback API, but the latter could (eventually) provide better guidance and validation.

@lgarron
Copy link
Member

lgarron commented Aug 9, 2025

I think it's also worth providing two default implementations with an easy way to invoke them:

  • prompt() — simple, always works, but not great UX.
  • A modal <dialog> implementation.

Both can certainly implement a simple callback API, but the latter could (eventually) provide better guidance and validation.

To be clear, I don't think these both need to be implemented immediately, just that the design makes it simple to invoke a modal implementation in the future.

@Kylo33
Copy link
Contributor Author

Kylo33 commented Aug 10, 2025

Thanks, that makes sense!

the main thing I would try to address is to make it more clear how/whether the implementation can be called as a fallback.

I added some extra information to the docstring for MacAddressProviderOption to try to make this more clear. I think it would be great to use for the QiYi cube, when getting the MAC address from the name fails, and for the Gan cube if watchAdvertisements() fails, or is not available. If support is added for a cube that doesn't have a way to automatically find the MAC address, it would be good to use this as a first step as well.

/**
 * Type of MAC address provider to use when a bluetooth device's MAC address
 * is needed and cannot automatically be determined, and as a fallback when
 * automatically determining the MAC address fails.
 *
 * - `"PROMPT"`: Use the built-in prompt-based provider (default).
 * - `"DIALOG"`: Use the built-in dialog-based provider.
 * - {@link MacAddressProvider}: Use a custom MAC address provider.
 */
export type MacAddressProviderOption =
  | "PROMPT"
  | "DIALOG"
  | MacAddressProvider
  | undefined;

I made a default <dialog> implementation as well, but it is very primitive, and I'm planning to continue working on it.

I also think it's important to have a way for the user to cancel the MAC address input, so I added a MacAddressCancelledError, which extends Error, and this could be thrown by any MacAddressProvider, causing the bluetooth device to disconnect or not continue connecting. I'm not sure if this is a great way to handle this, because someone who is creating a custom MacAddressProvider might not find the custom error class.

I'm happy to hear other ideas about anything too! Thanks

@lgarron
Copy link
Member

lgarron commented Aug 15, 2025

Apologies, I haven't taken the time to look at this in detail because it's still marked as a draft; do you think it's complete enough for me to review in detail?

@Kylo33 Kylo33 marked this pull request as ready for review August 15, 2025 19:31
@Kylo33
Copy link
Contributor Author

Kylo33 commented Aug 15, 2025

Apologies, I haven't taken the time to look at this in detail because it's still marked as a draft; do you think it's complete enough for me to review in detail?

Yes, I think it is in a good state to review. I'll mark it as 'Ready for review.' Thanks!

@lgarron lgarron force-pushed the main branch 6 times, most recently from 50271bc to 690a170 Compare October 31, 2025 20:02
@lgarron lgarron force-pushed the main branch 5 times, most recently from f4857cf to 37191fe Compare November 19, 2025 10:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants