@daugasauron/dolly-host-microphone 0.1.5-rc.0 → 0.1.6

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.
package/microphone.mjs CHANGED
@@ -17,7 +17,7 @@ function indicate({ mount, indicators }, state) {
17
17
  element.className = "page-indicator";
18
18
  element.setAttribute("role", "status");
19
19
  element.style.cssText = "position:absolute;left:0.5rem;bottom:2rem;z-index:8;max-width:calc(100% - 1rem);" +
20
- "padding:0.15rem 0.45rem;background:#262626;border:1px solid #e98773;color:#e98773;font-size:13px;pointer-events:none";
20
+ "padding:0.15rem 0.45rem;background:var(--dolly-ui-background);border:1px solid var(--dolly-ui-alert);color:var(--dolly-ui-alert);font-size:13px;pointer-events:none";
21
21
  }
22
22
  const text = { waiting: "Microphone: waiting for permission", capturing: "Microphone on" }[state];
23
23
  element.hidden = !text;
@@ -25,8 +25,30 @@ function indicate({ mount, indicators }, state) {
25
25
  indicators.hold(element, Boolean(text));
26
26
  }
27
27
 
28
- export function browser({ mount, send, indicators }) {
29
- const provider = createMicrophoneProvider(() => indicate({ mount, indicators }, provider.status().state));
28
+ // A session that enabled the module itself (nena@0, `switched`) captures only
29
+ // after the user says yes on the page, once a session: the browser's own
30
+ // prompt is per origin and remembered, so it may not ask again.
31
+ function asked(mount) {
32
+ return new Promise(resolve => {
33
+ const element = mount.appendChild(document.createElement("div"));
34
+ element.setAttribute("role", "alertdialog");
35
+ element.style.cssText = "position:absolute;left:50%;top:40%;transform:translate(-50%,-50%);z-index:9;padding:0.8rem 1rem;" +
36
+ "background:#262626;border:1px solid #e98773;color:#e8e3d7;font-size:14px;max-width:min(30rem,calc(100% - 2rem))";
37
+ element.textContent = "A program in this session asks to use the microphone.";
38
+ const choice = allowed => () => { element.remove(); resolve(allowed); };
39
+ for (const [label, allowed] of [["Allow", true], ["Refuse", false]]) {
40
+ const button = Object.assign(document.createElement("button"), { type: "button", textContent: label });
41
+ button.style.cssText = "margin:0.6rem 0.6rem 0 0;padding:0.2rem 0.9rem;color:inherit;background:transparent;border:1px solid currentColor";
42
+ button.addEventListener("click", choice(allowed));
43
+ element.append(button);
44
+ }
45
+ });
46
+ }
47
+
48
+ export function browser({ mount, send, indicators, switched }) {
49
+ let consented = !switched;
50
+ const consent = async () => consented ||= await asked(mount);
51
+ const provider = createMicrophoneProvider(() => indicate({ mount, indicators }, provider.status().state), consent);
30
52
  return {
31
53
  page: { get microphone() { return provider.status(); } },
32
54
  messages: {
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
- "version": "0.1.5-rc.0",
2
+ "version": "0.1.6",
3
3
  "type": "module",
4
4
  "name": "@daugasauron/dolly-host-microphone",
5
5
  "license": "MIT",
6
6
  "dependencies": {
7
- "@daugasauron/dolly": "0.1.5-rc.0"
7
+ "@daugasauron/dolly": "0.1.6"
8
8
  },
9
9
  "description": "Dolly's microphone@0 host module: the default microphone, behind the browser's permission prompt.",
10
10
  "repository": {
package/provider.mjs CHANGED
@@ -8,8 +8,9 @@ const names = { [M.DOLLY_MICROPHONE_WAITING]: "waiting", [M.DOLLY_MICROPHONE_CAP
8
8
 
9
9
  // The page side of microphone@0: one capture of the browser's default input.
10
10
  // OPEN asks the browser, whose own prompt decides; nothing here grants it.
11
- // `changed` runs whenever the capture's state does.
12
- export function createMicrophoneProvider(changed = () => {}) {
11
+ // `changed` runs whenever the capture's state does; `consent` resolves to
12
+ // whether the user lets the capture go ahead, before the browser is asked.
13
+ export function createMicrophoneProvider(changed = () => {}, consent = async () => true) {
13
14
  let capture, generation = 0;
14
15
 
15
16
  function set(target, state) {
@@ -36,6 +37,8 @@ export function createMicrophoneProvider(changed = () => {}) {
36
37
  }
37
38
  async function start(target) {
38
39
  try {
40
+ if (!await consent()) return set(target, M.DOLLY_MICROPHONE_DENIED);
41
+ if (target.released) return;
39
42
  target.stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
40
43
  if (target.released) return stop(target);
41
44
  target.context = new AudioContext({ sampleRate: M.DOLLY_MICROPHONE_RATE });