@ambasada/miniapp 0.5.0 → 0.6.0

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/dist/index.d.ts CHANGED
@@ -142,6 +142,15 @@ export declare const auth: {
142
142
  token: string;
143
143
  expires_at: string;
144
144
  }>;
145
+ /**
146
+ * Silent sign-in in one call: gets a shell token and POSTs it to your server (`/api/auth/shell` in the templates,
147
+ * `village.auth.shell(req)` in @ambasada/sdk), which sets your session cookie. Resolves the server's `user`, or
148
+ * null when the shell can't do it (outside the app, an older app, an app that is not verified): then show
149
+ * "Sign in with Ambasada". Rejects when your server refuses the token.
150
+ */
151
+ signIn<User = unknown>(options?: {
152
+ endpoint?: string;
153
+ }): Promise<User | null>;
145
154
  };
146
155
  export declare const notifications: {
147
156
  /**
@@ -162,12 +171,14 @@ export declare function on(event: "notification", handler: (n: {
162
171
  }) => void): () => void;
163
172
  /**
164
173
  * Local development: pretend to be inside the Ambasada app. Installs a version 3 shell with the given
165
- * context, records every message in `messages`, answers requests (`getToken` → a fake token, `request()` →
166
- * enabled) and returns `emit` to fire shell events. `ambasada dev` calls this for you.
174
+ * context, records every message in `messages`, answers requests (`getToken` → a fake token, or with `issuer`
175
+ * (the `ambasada dev` mock URL) a real signed one from `<issuer>/dev/shell-token`; `request()` → enabled)
176
+ * and returns `emit` to fire shell events. The templates call it on `?ambasada_shell=mock` under `ambasada dev`.
167
177
  */
168
178
  export declare function mockEnv(overrides?: Partial<ShellContext> & {
169
179
  version?: number;
170
180
  capabilities?: Capability[];
181
+ issuer?: string;
171
182
  answers?: Partial<Record<"auth.getToken" | "notifications.request", unknown | Error>>;
172
183
  }): {
173
184
  messages: Message[];
@@ -202,6 +213,15 @@ declare const _default: {
202
213
  token: string;
203
214
  expires_at: string;
204
215
  }>;
216
+ /**
217
+ * Silent sign-in in one call: gets a shell token and POSTs it to your server (`/api/auth/shell` in the templates,
218
+ * `village.auth.shell(req)` in @ambasada/sdk), which sets your session cookie. Resolves the server's `user`, or
219
+ * null when the shell can't do it (outside the app, an older app, an app that is not verified): then show
220
+ * "Sign in with Ambasada". Rejects when your server refuses the token.
221
+ */
222
+ signIn<User = unknown>(options?: {
223
+ endpoint?: string;
224
+ }): Promise<User | null>;
205
225
  };
206
226
  notifications: {
207
227
  /**
package/dist/index.js CHANGED
@@ -128,6 +128,25 @@ var auth = {
128
128
  getToken() {
129
129
  if (!has("auth.getToken")) return Promise.reject(new Error(has("ready") ? "silent sign-in is not available for this app" : NOT_IN_AMBASADA));
130
130
  return request("auth.getToken");
131
+ },
132
+ /**
133
+ * Silent sign-in in one call: gets a shell token and POSTs it to your server (`/api/auth/shell` in the templates,
134
+ * `village.auth.shell(req)` in @ambasada/sdk), which sets your session cookie. Resolves the server's `user`, or
135
+ * null when the shell can't do it (outside the app, an older app, an app that is not verified): then show
136
+ * "Sign in with Ambasada". Rejects when your server refuses the token.
137
+ */
138
+ async signIn(options = {}) {
139
+ const w = win();
140
+ if (!w || !has("auth.getToken")) return null;
141
+ let token;
142
+ try {
143
+ token = (await request("auth.getToken")).token;
144
+ } catch {
145
+ return null;
146
+ }
147
+ const res = await w.fetch(options.endpoint ?? "/api/auth/shell", { method: "POST", credentials: "same-origin", headers: { "content-type": "application/json" }, body: JSON.stringify({ token }) });
148
+ if (!res.ok) throw new Error(`silent sign-in refused (${res.status})`);
149
+ return (await res.json()).user;
131
150
  }
132
151
  };
133
152
  var notifications = {
@@ -158,7 +177,7 @@ function on(event, handler) {
158
177
  function mockEnv(overrides = {}) {
159
178
  const w = win();
160
179
  if (!w) throw new Error("mockEnv needs a window");
161
- const { version = 3, capabilities: caps, answers = {}, ...ctx } = overrides;
180
+ const { version = 3, capabilities: caps, answers = {}, issuer, ...ctx } = overrides;
162
181
  const messages = [];
163
182
  w.ambasadaShell = {
164
183
  version,
@@ -178,9 +197,14 @@ function mockEnv(overrides = {}) {
178
197
  const m = JSON.parse(raw);
179
198
  messages.push(m);
180
199
  if (m.type === "request") {
181
- const a = m.method in answers ? answers[m.method] : m.method === "auth.getToken" ? { token: "fake.jwt.token", expires_at: new Date(Date.now() + 6e4).toISOString() } : { enabled: true };
182
- const detail = a instanceof Error ? { id: m.id, error: a.message } : { id: m.id, result: a };
183
- setTimeout(() => w.dispatchEvent(new CustomEvent("ambasada:reply", { detail })), 0);
200
+ const reply = (a) => {
201
+ const detail = a instanceof Error ? { id: m.id, error: a.message } : { id: m.id, result: a };
202
+ setTimeout(() => w.dispatchEvent(new CustomEvent("ambasada:reply", { detail })), 0);
203
+ };
204
+ if (m.method in answers) reply(answers[m.method]);
205
+ else if (m.method === "auth.getToken" && issuer) {
206
+ w.fetch(`${issuer.replace(/\/+$/, "")}/dev/shell-token?aud=${encodeURIComponent(w.location.origin)}`).then((r) => r.ok ? r.json() : Promise.reject(new Error(`mock issuer answered ${r.status}`))).then(reply, reply);
207
+ } else reply(m.method === "auth.getToken" ? { token: "fake.jwt.token", expires_at: new Date(Date.now() + 6e4).toISOString() } : { enabled: true });
184
208
  }
185
209
  }
186
210
  };
package/dist/miniapp.js CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";var __ambasadaMiniapp=(()=>{var v=Object.defineProperty;var F=Object.getOwnPropertyDescriptor;var I=Object.getOwnPropertyNames;var O=Object.prototype.hasOwnProperty;var j=(e,t)=>{for(var n in t)v(e,n,{get:t[n],enumerable:!0})},D=(e,t,n,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of I(t))!O.call(e,a)&&a!==n&&v(e,a,{get:()=>t[a],enumerable:!(i=F(t,a))||i.enumerable});return e};var R=e=>D(v({},"__esModule",{value:!0}),e);var J={};j(J,{applyCssVars:()=>u,auth:()=>$,capabilities:()=>x,close:()=>C,context:()=>b,default:()=>H,haptics:()=>P,isInAmbasada:()=>g,mockEnv:()=>U,notifications:()=>M,on:()=>q,openApp:()=>T,openUrl:()=>A,ready:()=>S,setTitle:()=>_,share:()=>L,shell:()=>d});var W={top:0,bottom:0,left:0,right:0},k="not inside the Ambasada app",s=()=>typeof window>"u"?null:window;function d(){let e=s()?.ambasadaShell;return e&&typeof e.version=="number"&&e.version>=2?e:null}function g(){let e=s();return e?d()!==null||/AmbasadaChat\//.test(e.navigator.userAgent):!1}function b(){let e=d();return{app:e?.app??null,platform:e?.platform??"web",safeArea:{...W,...e?.safeArea??{}},hostBar:e?.hostBar??0,theme:e?.theme??{accent:"#FF002E",scheme:"light"},locale:e?.locale??s()?.navigator.language??"en",user:e?.user??null,location:e?.location??{type:"launcher"}}}function x(){let e=d();return e?e.capabilities?e.capabilities:e.version>=3?["ready","openUrl","openApp","close","share","haptics","title","auth.getToken","notifications"]:["openUrl","close"]:[]}var r=e=>x().includes(e);function o(e){let t=s();if(!t||!g())return!1;if(t.webkit?.messageHandlers?.ambasada)t.webkit.messageHandlers.ambasada.postMessage(e);else if(t.ambasada?.postMessage)t.ambasada.postMessage(JSON.stringify(e));else return!1;return!0}var N=0;function E(e,t=15e3){let n=s();return new Promise((i,a)=>{let l=`${Date.now().toString(36)}-${++N}`,f=setTimeout(()=>{n?.removeEventListener("ambasada:reply",m),a(new Error(`${e}: the Ambasada app did not answer`))},t),m=w=>{let p=w.detail;!p||p.id!==l||(clearTimeout(f),n?.removeEventListener("ambasada:reply",m),p.error?a(new Error(p.error)):i(p.result))};n?.addEventListener("ambasada:reply",m),o({type:"request",id:l,method:e})||(clearTimeout(f),n?.removeEventListener("ambasada:reply",m),a(new Error(k)))})}function u(){let e=s();if(!e)return;let t=e.document.documentElement,n=b();for(let i of["top","bottom","left","right"])t.style.setProperty(`--ambasada-safe-area-${i}`,`${n.safeArea[i]}px`);t.style.setProperty("--ambasada-host-bar",`${n.hostBar}px`),t.classList.toggle("in-ambasada",g())}var h=!1;function S(){u(),!h&&(h=!0,r("ready")?o({type:"ready"}):d()?.version===2&&o({type:"chrome",header:"hidden",tabs:"hidden",back:"none",statusBar:"dark"}))}function A(e){r("openUrl")&&d().version>=3?o({type:"openUrl",url:e}):d()?.version===2?o({type:"open",url:e,mode:"external"}):s()?.open(e,"_blank","noopener")}function T(e,t="/"){r("openApp")?o({type:"openApp",app:e,path:t}):s()?.open(`https://ambasada.id/a/${encodeURIComponent(e)}${t.startsWith("/")?t:`/${t}`}`,"_blank","noopener")}function C(){r("close")?o({type:"close"}):s()?.history.back()}async function L(e={}){if(r("share")){o({type:"share",...e});return}let t=s();t?.navigator.share&&await t.navigator.share({url:e.url??t.location.href,text:e.text}).catch(()=>{})}var P={impact(e="light"){r("haptics")&&o({type:"haptic",kind:"impact",style:e})},notification(e="success"){r("haptics")&&o({type:"haptic",kind:"notification",style:e})},selection(){r("haptics")&&o({type:"haptic",kind:"selection"})}};function _(e){let t=s();t&&(t.document.title=e),r("title")?o({type:"title",title:e}):d()?.version===2&&o({type:"chrome",title:e})}var $={getToken(){return r("auth.getToken")?E("auth.getToken"):Promise.reject(new Error(r("ready")?"silent sign-in is not available for this app":k))}},M={request(){return r("notifications")?E("notifications.request"):Promise.resolve({enabled:!1})}};function q(e,t){let n=s();if(!n)return()=>{};let i=a=>{let l=a.detail;e==="context"?(l&&typeof l=="object"&&Object.assign(n.ambasadaShell??(n.ambasadaShell={version:3,platform:"web"}),l),u(),t(b())):t(l)};return n.addEventListener(`ambasada:${e}`,i),()=>n.removeEventListener(`ambasada:${e}`,i)}function U(e={}){let t=s();if(!t)throw new Error("mockEnv needs a window");let{version:n=3,capabilities:i,answers:a={},...l}=e,f=[];return t.ambasadaShell={version:n,platform:"ios",safeArea:{top:59,bottom:34,left:0,right:0},hostBar:44,theme:{accent:"#FF002E",scheme:"light"},locale:"ro-RO",app:{id:"my-company",tier:"unlisted"},user:{id:"user_fake",name:"Fake Visitor",avatar:null},location:{type:"launcher"},...i?{capabilities:i}:{},...l},t.ambasada={postMessage(p){let c=JSON.parse(p);if(f.push(c),c.type==="request"){let y=c.method in a?a[c.method]:c.method==="auth.getToken"?{token:"fake.jwt.token",expires_at:new Date(Date.now()+6e4).toISOString()}:{enabled:!0},B=y instanceof Error?{id:c.id,error:y.message}:{id:c.id,result:y};setTimeout(()=>t.dispatchEvent(new CustomEvent("ambasada:reply",{detail:B})),0)}}},h=!1,u(),{messages:f,emit:(p,c)=>t.dispatchEvent(new CustomEvent(`ambasada:${p}`,{detail:c})),uninstall:()=>{delete t.ambasadaShell,delete t.ambasada,h=!1,u()}}}var H={shell:d,isInAmbasada:g,context:b,capabilities:x,applyCssVars:u,ready:S,openUrl:A,openApp:T,close:C,share:L,haptics:P,setTitle:_,auth:$,notifications:M,on:q,mockEnv:U};return R(J);})();
1
+ "use strict";var __ambasadaMiniapp=(()=>{var x=Object.defineProperty;var O=Object.getOwnPropertyDescriptor;var F=Object.getOwnPropertyNames;var R=Object.prototype.hasOwnProperty;var D=(e,t)=>{for(var n in t)x(e,n,{get:t[n],enumerable:!0})},N=(e,t,n,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of F(t))!R.call(e,s)&&s!==n&&x(e,s,{get:()=>t[s],enumerable:!(a=O(t,s))||a.enumerable});return e};var W=e=>N(x({},"__esModule",{value:!0}),e);var z={};D(z,{applyCssVars:()=>f,auth:()=>M,capabilities:()=>E,close:()=>P,context:()=>v,default:()=>V,haptics:()=>L,isInAmbasada:()=>y,mockEnv:()=>B,notifications:()=>q,on:()=>j,openApp:()=>$,openUrl:()=>C,ready:()=>T,setTitle:()=>_,share:()=>U,shell:()=>c});var H={top:0,bottom:0,left:0,right:0},A="not inside the Ambasada app",i=()=>typeof window>"u"?null:window;function c(){let e=i()?.ambasadaShell;return e&&typeof e.version=="number"&&e.version>=2?e:null}function y(){let e=i();return e?c()!==null||/AmbasadaChat\//.test(e.navigator.userAgent):!1}function v(){let e=c();return{app:e?.app??null,platform:e?.platform??"web",safeArea:{...H,...e?.safeArea??{}},hostBar:e?.hostBar??0,theme:e?.theme??{accent:"#FF002E",scheme:"light"},locale:e?.locale??i()?.navigator.language??"en",user:e?.user??null,location:e?.location??{type:"launcher"}}}function E(){let e=c();return e?e.capabilities?e.capabilities:e.version>=3?["ready","openUrl","openApp","close","share","haptics","title","auth.getToken","notifications"]:["openUrl","close"]:[]}var o=e=>E().includes(e);function r(e){let t=i();if(!t||!y())return!1;if(t.webkit?.messageHandlers?.ambasada)t.webkit.messageHandlers.ambasada.postMessage(e);else if(t.ambasada?.postMessage)t.ambasada.postMessage(JSON.stringify(e));else return!1;return!0}var J=0;function k(e,t=15e3){let n=i();return new Promise((a,s)=>{let l=`${Date.now().toString(36)}-${++J}`,m=setTimeout(()=>{n?.removeEventListener("ambasada:reply",d),s(new Error(`${e}: the Ambasada app did not answer`))},t),d=S=>{let h=S.detail;!h||h.id!==l||(clearTimeout(m),n?.removeEventListener("ambasada:reply",d),h.error?s(new Error(h.error)):a(h.result))};n?.addEventListener("ambasada:reply",d),r({type:"request",id:l,method:e})||(clearTimeout(m),n?.removeEventListener("ambasada:reply",d),s(new Error(A)))})}function f(){let e=i();if(!e)return;let t=e.document.documentElement,n=v();for(let a of["top","bottom","left","right"])t.style.setProperty(`--ambasada-safe-area-${a}`,`${n.safeArea[a]}px`);t.style.setProperty("--ambasada-host-bar",`${n.hostBar}px`),t.classList.toggle("in-ambasada",y())}var b=!1;function T(){f(),!b&&(b=!0,o("ready")?r({type:"ready"}):c()?.version===2&&r({type:"chrome",header:"hidden",tabs:"hidden",back:"none",statusBar:"dark"}))}function C(e){o("openUrl")&&c().version>=3?r({type:"openUrl",url:e}):c()?.version===2?r({type:"open",url:e,mode:"external"}):i()?.open(e,"_blank","noopener")}function $(e,t="/"){o("openApp")?r({type:"openApp",app:e,path:t}):i()?.open(`https://ambasada.id/a/${encodeURIComponent(e)}${t.startsWith("/")?t:`/${t}`}`,"_blank","noopener")}function P(){o("close")?r({type:"close"}):i()?.history.back()}async function U(e={}){if(o("share")){r({type:"share",...e});return}let t=i();t?.navigator.share&&await t.navigator.share({url:e.url??t.location.href,text:e.text}).catch(()=>{})}var L={impact(e="light"){o("haptics")&&r({type:"haptic",kind:"impact",style:e})},notification(e="success"){o("haptics")&&r({type:"haptic",kind:"notification",style:e})},selection(){o("haptics")&&r({type:"haptic",kind:"selection"})}};function _(e){let t=i();t&&(t.document.title=e),o("title")?r({type:"title",title:e}):c()?.version===2&&r({type:"chrome",title:e})}var M={getToken(){return o("auth.getToken")?k("auth.getToken"):Promise.reject(new Error(o("ready")?"silent sign-in is not available for this app":A))},async signIn(e={}){let t=i();if(!t||!o("auth.getToken"))return null;let n;try{n=(await k("auth.getToken")).token}catch{return null}let a=await t.fetch(e.endpoint??"/api/auth/shell",{method:"POST",credentials:"same-origin",headers:{"content-type":"application/json"},body:JSON.stringify({token:n})});if(!a.ok)throw new Error(`silent sign-in refused (${a.status})`);return(await a.json()).user}},q={request(){return o("notifications")?k("notifications.request"):Promise.resolve({enabled:!1})}};function j(e,t){let n=i();if(!n)return()=>{};let a=s=>{let l=s.detail;e==="context"?(l&&typeof l=="object"&&Object.assign(n.ambasadaShell??(n.ambasadaShell={version:3,platform:"web"}),l),f(),t(v())):t(l)};return n.addEventListener(`ambasada:${e}`,a),()=>n.removeEventListener(`ambasada:${e}`,a)}function B(e={}){let t=i();if(!t)throw new Error("mockEnv needs a window");let{version:n=3,capabilities:a,answers:s={},issuer:l,...m}=e,d=[];return t.ambasadaShell={version:n,platform:"ios",safeArea:{top:59,bottom:34,left:0,right:0},hostBar:44,theme:{accent:"#FF002E",scheme:"light"},locale:"ro-RO",app:{id:"my-company",tier:"unlisted"},user:{id:"user_fake",name:"Fake Visitor",avatar:null},location:{type:"launcher"},...a?{capabilities:a}:{},...m},t.ambasada={postMessage(w){let p=JSON.parse(w);if(d.push(p),p.type==="request"){let g=u=>{let I=u instanceof Error?{id:p.id,error:u.message}:{id:p.id,result:u};setTimeout(()=>t.dispatchEvent(new CustomEvent("ambasada:reply",{detail:I})),0)};p.method in s?g(s[p.method]):p.method==="auth.getToken"&&l?t.fetch(`${l.replace(/\/+$/,"")}/dev/shell-token?aud=${encodeURIComponent(t.location.origin)}`).then(u=>u.ok?u.json():Promise.reject(new Error(`mock issuer answered ${u.status}`))).then(g,g):g(p.method==="auth.getToken"?{token:"fake.jwt.token",expires_at:new Date(Date.now()+6e4).toISOString()}:{enabled:!0})}}},b=!1,f(),{messages:d,emit:(w,p)=>t.dispatchEvent(new CustomEvent(`ambasada:${w}`,{detail:p})),uninstall:()=>{delete t.ambasadaShell,delete t.ambasada,b=!1,f()}}}var V={shell:c,isInAmbasada:y,context:v,capabilities:E,applyCssVars:f,ready:T,openUrl:C,openApp:$,close:P,share:U,haptics:L,setTitle:_,auth:M,notifications:q,on:j,mockEnv:B};return W(z);})();
2
2
  (function (g) {
3
3
  var prev = g.ambasada, api = Object.assign({}, __ambasadaMiniapp.default, __ambasadaMiniapp);
4
4
  delete api.default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ambasada/miniapp",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Run your web app inside the Ambasada app as a mini app: ready(), shell context, safe areas, open links, haptics, notifications. Zero dependencies; also a <script> build.",
5
5
  "type": "module",
6
6
  "license": "MIT",