A place to cache linked articles (think custom and personal wayback machine)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang=fr>
  4. <!-- Has to be within the first 1024 bytes, hence before the <title>
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset=utf-8>
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name=viewport content="width=device-width,minimum-scale=1,initial-scale=1,shrink-to-fit=no">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>I built a PWA and published it in 3 app stores. Here’s what I learned. (archive) — David Larlet</title>
  13. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  14. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons/apple-touch-icon.png">
  15. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons/favicon-32x32.png">
  16. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons/favicon-16x16.png">
  17. <link rel="manifest" href="/manifest.json">
  18. <link rel="mask-icon" href="/static/david/icons/safari-pinned-tab.svg" color="#5bbad5">
  19. <link rel="shortcut icon" href="/static/david/icons/favicon.ico">
  20. <meta name="apple-mobile-web-app-title" content="David Larlet">
  21. <meta name="application-name" content="David Larlet">
  22. <meta name="msapplication-TileColor" content="#da532c">
  23. <meta name="msapplication-config" content="/static/david/icons/browserconfig.xml">
  24. <meta name="theme-color" content="#f0f0ea">
  25. <!-- That good ol' feed, subscribe :p. -->
  26. <link rel=alternate type="application/atom+xml" title=Feed href="/david/log/">
  27. <meta name="robots" content="noindex, nofollow">
  28. <meta content="origin-when-cross-origin" name="referrer">
  29. <!-- Canonical URL for SEO purposes -->
  30. <link rel="canonical" href="http://debuggerdotbreak.judahgabriel.com/2018/04/13/i-built-a-pwa-and-published-it-in-3-app-stores-heres-what-i-learned/">
  31. <style>
  32. /* http://meyerweb.com/eric/tools/css/reset/ */
  33. html, body, div, span,
  34. h1, h2, h3, h4, h5, h6, p, blockquote, pre,
  35. a, abbr, address, big, cite, code,
  36. del, dfn, em, img, ins,
  37. small, strike, strong, tt, var,
  38. dl, dt, dd, ol, ul, li,
  39. fieldset, form, label, legend,
  40. table, caption, tbody, tfoot, thead, tr, th, td,
  41. article, aside, canvas, details, embed,
  42. figure, figcaption, footer, header, hgroup,
  43. menu, nav, output, ruby, section, summary,
  44. time, mark, audio, video {
  45. margin: 0;
  46. padding: 0;
  47. border: 0;
  48. font-size: 100%;
  49. font: inherit;
  50. vertical-align: baseline;
  51. }
  52. /* HTML5 display-role reset for older browsers */
  53. article, aside, details, figcaption, figure,
  54. footer, header, hgroup, menu, nav, section { display: block; }
  55. body { line-height: 1; }
  56. blockquote, q { quotes: none; }
  57. blockquote:before, blockquote:after,
  58. q:before, q:after {
  59. content: '';
  60. content: none;
  61. }
  62. table {
  63. border-collapse: collapse;
  64. border-spacing: 0;
  65. }
  66. /* http://practicaltypography.com/equity.html */
  67. /* https://calendar.perfplanet.com/2016/no-font-face-bulletproof-syntax/ */
  68. /* https://www.filamentgroup.com/lab/js-web-fonts.html */
  69. @font-face {
  70. font-family: 'EquityTextB';
  71. src: url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff2') format('woff2'),
  72. url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff') format('woff');
  73. font-weight: 300;
  74. font-style: normal;
  75. font-display: swap;
  76. }
  77. @font-face {
  78. font-family: 'EquityTextB';
  79. src: url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff2') format('woff2'),
  80. url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff') format('woff');
  81. font-weight: 300;
  82. font-style: italic;
  83. font-display: swap;
  84. }
  85. @font-face {
  86. font-family: 'EquityTextB';
  87. src: url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff2') format('woff2'),
  88. url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff') format('woff');
  89. font-weight: 700;
  90. font-style: normal;
  91. font-display: swap;
  92. }
  93. @font-face {
  94. font-family: 'ConcourseT3';
  95. src: url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff2') format('woff2'),
  96. url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff') format('woff');
  97. font-weight: 300;
  98. font-style: normal;
  99. font-display: swap;
  100. }
  101. /* http://practice.typekit.com/lesson/caring-about-opentype-features/ */
  102. body {
  103. /* http://www.cssfontstack.com/ Palatino 99% Win 86% Mac */
  104. font-family: "EquityTextB", Palatino, serif;
  105. background-color: #f0f0ea;
  106. color: #07486c;
  107. font-kerning: normal;
  108. -moz-osx-font-smoothing: grayscale;
  109. -webkit-font-smoothing: subpixel-antialiased;
  110. text-rendering: optimizeLegibility;
  111. font-variant-ligatures: common-ligatures contextual;
  112. font-feature-settings: "kern", "liga", "clig", "calt";
  113. }
  114. pre, code, kbd, samp, var, tt {
  115. font-family: 'TriplicateT4c', monospace;
  116. }
  117. em {
  118. font-style: italic;
  119. color: #323a45;
  120. }
  121. strong {
  122. font-weight: bold;
  123. color: black;
  124. }
  125. nav {
  126. background-color: #323a45;
  127. color: #f0f0ea;
  128. display: flex;
  129. justify-content: space-around;
  130. padding: 1rem .5rem;
  131. }
  132. nav:last-child {
  133. border-bottom: 1vh solid #2d7474;
  134. }
  135. nav a {
  136. color: #f0f0ea;
  137. }
  138. nav abbr {
  139. border-bottom: 1px dotted white;
  140. }
  141. h1 {
  142. border-top: 1vh solid #2d7474;
  143. border-bottom: .2vh dotted #2d7474;
  144. background-color: #e3e1e1;
  145. color: #323a45;
  146. text-align: center;
  147. padding: 5rem 0 4rem 0;
  148. width: 100%;
  149. font-family: 'ConcourseT3';
  150. display: flex;
  151. flex-direction: column;
  152. }
  153. h1.single {
  154. padding-bottom: 10rem;
  155. }
  156. h1 span {
  157. position: absolute;
  158. top: 1vh;
  159. left: 20%;
  160. line-height: 0;
  161. }
  162. h1 span a {
  163. line-height: 1.7;
  164. padding: 1rem 1.2rem .6rem 1.2rem;
  165. border-radius: 0 0 6% 6%;
  166. background: #2d7474;
  167. font-size: 1.3rem;
  168. color: white;
  169. text-decoration: none;
  170. }
  171. h2 {
  172. margin: 4rem 0 1rem;
  173. border-top: .2vh solid #2d7474;
  174. padding-top: 1vh;
  175. }
  176. h3 {
  177. text-align: center;
  178. margin: 3rem 0 .75em;
  179. }
  180. hr {
  181. height: .4rem;
  182. width: .4rem;
  183. border-radius: .4rem;
  184. background: #07486c;
  185. margin: 2.5rem auto;
  186. }
  187. time {
  188. display: bloc;
  189. margin-left: 0 !important;
  190. }
  191. ul, ol {
  192. margin: 2rem;
  193. }
  194. ul {
  195. list-style-type: square;
  196. }
  197. a {
  198. text-decoration-skip-ink: auto;
  199. text-decoration-thickness: 0.05em;
  200. text-underline-offset: 0.09em;
  201. }
  202. article {
  203. max-width: 50rem;
  204. display: flex;
  205. flex-direction: column;
  206. margin: 2rem auto;
  207. }
  208. article.single {
  209. border-top: .2vh dotted #2d7474;
  210. margin: -6rem auto 1rem auto;
  211. background: #f0f0ea;
  212. padding: 2rem;
  213. }
  214. article p:last-child {
  215. margin-bottom: 1rem;
  216. }
  217. p {
  218. padding: 0 .5rem;
  219. margin-left: 3rem;
  220. }
  221. p + p,
  222. figure + p {
  223. margin-top: 2rem;
  224. }
  225. blockquote {
  226. background-color: #e3e1e1;
  227. border-left: .5vw solid #2d7474;
  228. display: flex;
  229. flex-direction: column;
  230. align-items: center;
  231. padding: 1rem;
  232. margin: 1.5rem;
  233. }
  234. blockquote cite {
  235. font-style: italic;
  236. }
  237. blockquote p {
  238. margin-left: 0;
  239. }
  240. figure {
  241. border-top: .2vh solid #2d7474;
  242. background-color: #e3e1e1;
  243. text-align: center;
  244. padding: 1.5rem 0;
  245. margin: 1rem 0 0;
  246. font-size: 1.5rem;
  247. width: 100%;
  248. }
  249. figure img {
  250. max-width: 250px;
  251. max-height: 250px;
  252. border: .5vw solid #323a45;
  253. padding: 1px;
  254. }
  255. figcaption {
  256. padding: 1rem;
  257. line-height: 1.4;
  258. }
  259. aside {
  260. display: flex;
  261. flex-direction: column;
  262. background-color: #e3e1e1;
  263. padding: 1rem 0;
  264. border-bottom: .2vh solid #07486c;
  265. }
  266. aside p {
  267. max-width: 50rem;
  268. margin: 0 auto;
  269. }
  270. /* https://fvsch.com/code/css-locks/ */
  271. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  272. font-size: 1rem;
  273. line-height: calc( 1.5em + 0.2 * 1rem );
  274. }
  275. h1 {
  276. font-size: 1.9rem;
  277. line-height: calc( 1.2em + 0.2 * 1rem );
  278. }
  279. h2 {
  280. font-size: 1.6rem;
  281. line-height: calc( 1.3em + 0.2 * 1rem );
  282. }
  283. h3 {
  284. font-size: 1.35rem;
  285. line-height: calc( 1.4em + 0.2 * 1rem );
  286. }
  287. @media (min-width: 20em) {
  288. /* The (100vw - 20rem) / (50 - 20) part
  289. resolves to 0-1rem, depending on the
  290. viewport width (between 20em and 50em). */
  291. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  292. font-size: calc( 1rem + .6 * (100vw - 20rem) / (50 - 20) );
  293. line-height: calc( 1.5em + 0.2 * (100vw - 50rem) / (20 - 50) );
  294. margin-left: 0;
  295. }
  296. h1 {
  297. font-size: calc( 1.9rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  298. line-height: calc( 1.2em + 0.2 * (100vw - 50rem) / (20 - 50) );
  299. }
  300. h2 {
  301. font-size: calc( 1.5rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  302. line-height: calc( 1.3em + 0.2 * (100vw - 50rem) / (20 - 50) );
  303. }
  304. h3 {
  305. font-size: calc( 1.35rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  306. line-height: calc( 1.4em + 0.2 * (100vw - 50rem) / (20 - 50) );
  307. }
  308. }
  309. @media (min-width: 50em) {
  310. /* The right part of the addition *must* be a
  311. rem value. In this example we *could* change
  312. the whole declaration to font-size:2.5rem,
  313. but if our baseline value was not expressed
  314. in rem we would have to use calc. */
  315. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  316. font-size: calc( 1rem + .6 * 1rem );
  317. line-height: 1.5em;
  318. }
  319. p, li, pre, details {
  320. margin-left: 3rem;
  321. }
  322. h1 {
  323. font-size: calc( 1.9rem + 1.5 * 1rem );
  324. line-height: 1.2em;
  325. }
  326. h2 {
  327. font-size: calc( 1.5rem + 1.5 * 1rem );
  328. line-height: 1.3em;
  329. }
  330. h3 {
  331. font-size: calc( 1.35rem + 1.5 * 1rem );
  332. line-height: 1.4em;
  333. }
  334. figure img {
  335. max-width: 500px;
  336. max-height: 500px;
  337. }
  338. }
  339. figure.unsquared {
  340. margin-bottom: 1.5rem;
  341. }
  342. figure.unsquared img {
  343. height: inherit;
  344. }
  345. @media print {
  346. body { font-size: 100%; }
  347. a:after { content: " (" attr(href) ")"; }
  348. a, a:link, a:visited, a:after {
  349. text-decoration: underline;
  350. text-shadow: none !important;
  351. background-image: none !important;
  352. background: white;
  353. color: black;
  354. }
  355. abbr[title] { border-bottom: 0; }
  356. abbr[title]:after { content: " (" attr(title) ")"; }
  357. img { page-break-inside: avoid; }
  358. @page { margin: 2cm .5cm; }
  359. h1, h2, h3 { page-break-after: avoid; }
  360. p3 { orphans: 3; widows: 3; }
  361. img {
  362. max-width: 250px !important;
  363. max-height: 250px !important;
  364. }
  365. nav, aside { display: none; }
  366. }
  367. ul.with_columns {
  368. column-count: 1;
  369. }
  370. @media (min-width: 20em) {
  371. ul.with_columns {
  372. column-count: 2;
  373. }
  374. }
  375. @media (min-width: 50em) {
  376. ul.with_columns {
  377. column-count: 3;
  378. }
  379. }
  380. ul.with_two_columns {
  381. column-count: 1;
  382. }
  383. @media (min-width: 20em) {
  384. ul.with_two_columns {
  385. column-count: 1;
  386. }
  387. }
  388. @media (min-width: 50em) {
  389. ul.with_two_columns {
  390. column-count: 2;
  391. }
  392. }
  393. .gallery {
  394. display: flex;
  395. flex-wrap: wrap;
  396. justify-content: space-around;
  397. }
  398. .gallery figure img {
  399. margin-left: 1rem;
  400. margin-right: 1rem;
  401. }
  402. .gallery figure figcaption {
  403. font-family: 'ConcourseT3'
  404. }
  405. footer {
  406. font-family: 'ConcourseT3';
  407. display: flex;
  408. flex-direction: column;
  409. border-top: 3px solid white;
  410. padding: 4rem 0;
  411. background-color: #07486c;
  412. color: white;
  413. }
  414. footer > * {
  415. max-width: 50rem;
  416. margin: 0 auto;
  417. }
  418. footer a {
  419. color: #f1c40f;
  420. }
  421. footer .avatar {
  422. width: 200px;
  423. height: 200px;
  424. border-radius: 50%;
  425. float: left;
  426. -webkit-shape-outside: circle();
  427. shape-outside: circle();
  428. margin-right: 2rem;
  429. padding: 2px 5px 5px 2px;
  430. background: white;
  431. border-left: 1px solid #f1c40f;
  432. border-top: 1px solid #f1c40f;
  433. border-right: 5px solid #f1c40f;
  434. border-bottom: 5px solid #f1c40f;
  435. }
  436. </style>
  437. <h1>
  438. <span><a id="jumper" href="#jumpto" title="Un peu perdu ?">?</a></span>
  439. I built a PWA and published it in 3 app stores. Here’s what I learned. (archive)
  440. <time>Pour la pérennité des contenus liés. Non-indexé, retrait sur simple email.</time>
  441. </h1>
  442. <section>
  443. <article>
  444. <h3><a href="http://debuggerdotbreak.judahgabriel.com/2018/04/13/i-built-a-pwa-and-published-it-in-3-app-stores-heres-what-i-learned/">Source originale du contenu</a></h3>
  445. <blockquote>
  446. <p><font>Summary</font>: Turning a web app into a Progressive Web App (PWA) and submitting it to 3 app stores requires about a month of work, a few hundred dollars, and lots of red tape.</p>
  447. </blockquote>
  448. <p>I recently published <a href="https://messianicradio.com">Chavah Messianic Radio</a>, a Pandora-like music player, as a Progressive Web App and submitted it to the 3 app stores (Google Play, iOS App Store, Windows Store).</p>
  449. <p><a href="https://itunes.apple.com/us/app/chavah-messianic-radio/id1361894819?platform=ipad&amp;preserveScrollPosition=true#platform/ipad"><img title="image" alt="image" src="http://debuggerdotbreak.judahgabriel.com/wp-content/uploads/2018/04/image.png" border="0"/></a></p>
  450. <p><a href="https://play.google.com/store/apps/details?id=com.messianicradio&amp;hl=en"><img title="image" alt="image" src="http://debuggerdotbreak.judahgabriel.com/wp-content/uploads/2018/04/image-1.png" border="0"/></a></p>
  451. <p><a href="https://www.microsoft.com/en-us/store/p/chavah/9nhkjb6lpptv"><img title="image" alt="image" src="http://debuggerdotbreak.judahgabriel.com/wp-content/uploads/2018/04/image-4.png" border="0"/></a></p>
  452. <p>The process was both painful and enlightening. Here’s what I learned.</p>
  453. <h3>Why?</h3>
  454. <p>First, you might wonder, “Why even put your app in the app stores? Just live on the opened web!” </p>
  455. <p>The answer, in a nutshell, is because <strong>that’s where the users are</strong>. We’ve trained a generation of users to find apps in proprietary app stores, not on the free and open web.</p>
  456. <p>For my web app, there were 2 big reasons to get in the app store: </p>
  457. <ol>
  458. <li>User demand </li>
  459. <li>Web app restrictions by <strike>Apple</strike> hostile mobile platforms</li>
  460. </ol>
  461. <p>User demand: My users have been asking me for years, “Is there an app for Chavah? I don’t see it in the store.” </p>
  462. <p>They ask that because we’ve trained users to look for apps in proprietary app stores.</p>
  463. <p>My response to my users has up until now been, </p>
  464. <blockquote>
  465. <p>“Aww, you don’t need an app – just go to the website on your phone! It works!”</p>
  466. </blockquote>
  467. <p>But I was kind of lying. </p>
  468. <p>Real web apps only kinda-sorta work on mobile. Which brings me to the 2nd reason: web app restrictions by <strike>Apple</strike> hostile mobile platforms.</p>
  469. <p>Mobile platform vendors, like Apple, are <em>totally cool </em>with apps that use your phone to its fullest. Access your location, play background audio, get your GPS coordinates, read all your contacts, play videos or audio without app interaction, read your email, intercept your typing, play more than one thing at a time, use your microphone and camera, access your pictures, and more. </p>
  470. <p>Apple’s totally cool with that.</p>
  471. <p><strong>But only if you pay Apple $99/year for the privilege.</strong></p>
  472. <p>If you want to do any of those things in a regular old web app, well, goshdarnit, Apple won’t just deny you these things, it <em>prevents you from even asking permission</em>. </p>
  473. <p>For my Pandora-like music player app, this <a href="http://debuggerdotbreak.judahgabriel.com/2016/12/13/its-almost-2017-and-html5-audio-is-still-broken-on-ios/">horrible brokenness showed up in numerous ways</a>.</p>
  474. <p>From minor things like “iOS Safari won’t let you play audio without first interacting with the page” to major, show-stopping things like, “iOS Safari won’t let you play the next song if your app is in the background or if your screen is off.”</p>
  475. <p>Oh, plus weird visual anomalies like <a href="https://stackoverflow.com/questions/46339063/ios-11-safari-bootstrap-modal-text-area-outside-of-cursor">typing in a textbox and seeing your text appear elsewhere on screen</a>.</p>
  476. <p>So, to make my HTML5 music app actually functional and working on people’s mobile devices, it was necessary to turn my PWA into an app in app store.</p>
  477. <h3>Barriers to entry</h3>
  478. <p>In the ideal world, publishing your web app to the app stores would look like this:</p>
  479. <p><hr/>
  480. <div>
  481. <p><strong>Your Web/Cloud Host or CI Provider</strong></p>
  482. <p>You’ve published a Progressive Web App. Publish to app stores?</p>
  483. <p>☑ iOS App Store<br/>☑ Google Play<br/>☑ Windows Store</p>
  484. </div>
  485. <hr/>
  486. <p>(Or alternately, as <a href="https://www.windowscentral.com/first-batch-windows-10-progressive-web-apps-here">Microsoft is experimenting with</a>, your PWA will just automatically appear in the app store as Bing crawls it.)</p>
  487. <p>But alas, we don’t live in this ideal world. Instead, we have to deal with all kinds of proprietary native BS to get our web apps in the stores.</p>
  488. <p>Each of the app stores has a barrier to entry: how difficult it is to take an existing web app and it in the app store.</p>
  489. <p>I list some of the barriers below.</p>
  490. <h3>Cost</h3>
  491. <hr/>
  492. <ul>
  493. <li><strong>Apple</strong>: $99/year to have your app listed in the iOS app store.<br/> </li>
  494. <li><strong>Google:</strong> One-time $25 fee to list your app in the Google Play Store.<br/> </li>
  495. <li><strong>Microsoft: </strong>Free!</li>
  496. </ul>
  497. <hr/>
  498. <p>Don’t make me pay you to make my app available to your users. My app enriches your platform. Without good apps, your platform will be abandoned.</p>
  499. <p>Apple used to understand this. When it first introduced the iPhone, Steve Jobs was adamant that HTML5 was the future and that apps will simply just be web apps. <a href="https://9to5mac.com/2011/10/21/jobs-original-vision-for-the-iphone-no-third-party-native-apps/">There was no native iPhone SDK</a> for 3rd parties. Apple has since abandoned this vision.</p>
  500. <p>Google asked for a token $25 one-time fee. Probably to avoid spammers and decrease truly junk apps from entering the store.</p>
  501. <p>Microsoft seems determined to just increase the total number of apps in their app store, regardless of quality.</p>
  502. <p><strong>Winner: </strong>Microsoft. It’s hard to beat free. </p>
  503. <h3>Adding native capabilities</h3>
  504. <p>In an ideal world, I wouldn’t have to write a single extra line of code for my web app to integrate into the OS. Or, as Steve Jobs <a href="https://9to5mac.com/2011/10/21/jobs-original-vision-for-the-iphone-no-third-party-native-apps/">said back in 2007</a>,</p>
  505. <blockquote>
  506. <p>“The full Safari engine is inside of iPhone. And so, you can write amazing Web 2.0 and Ajax apps that look exactly and behave exactly like apps on the iPhone. And these apps can integrate perfectly with iPhone services. They can make a call, they can send an email, they can look up a location on Google Maps.”</p>
  507. <p>-Steve Jobs, 2007</p>
  508. </blockquote>
  509. <p>For me, that means my web app plays background audio using standard HTML5 audio; that works just fine on all OSes.</p>
  510. <p>My web app declares what audio is playing, and the OSes pick up on that, show currently playing song info on the lock screen.</p>
  511. <p>My app controls audio using standard HTML5 audio API; the OS picks up on that and provides play/pause/next/volume/trackbar controls on the lock screen.</p>
  512. <p>But sadly, we don’t live in this ideal world. All the things listed above don’t actually work out of the box on all 3 platforms.</p>
  513. <p>My web app needs to play audio in the background. And load URLs from my CDN. Sounds reasonable, right? And bonus, how about showing currently playing song info on the lock screen? And controlling the audio (play/pause/next, etc.) from the lock screen? How hard is this?</p>
  514. <p>Three very different approaches taken here:</p>
  515. <hr/>
  516. <ul>
  517. <li><strong>Apple</strong>: We don’t give web apps a way to declare such capabilities; you’ll need to write a native wrapper (e.g. with Cordova) to interact with the OS. <br/> </li>
  518. <li><strong>Google</strong>: Web FTW! Let’s <a href="https://developers.google.com/web/updates/2017/02/media-session">create a new web standard</a> that shows audio &amp; controls from the lock screen. Background audio? Sure, go ahead!<br/> </li>
  519. <li><strong>Microsoft: </strong>We’ll inject our proprietary API, window.Windows.<em>, into your JavaScript global namespace and you can use that to do the things you want to do.</li>
  520. </ul>
  521. <hr/>
  522. <p>Going into more details here for each store:</p>
  523. <p>For iOS app store, does your web app need to play background audio? <a href="https://github.com/danielsogl/cordova-plugin-background-audio">Use a Cordova plugin</a>. Need to show currently playing song on the lock screen? <a href="https://github.com/leon/cordova-plugin-nowplaying">Use a Cordova plugin</a>. Need to control the currently playing song from the lock screen? <a href="https://github.com/leon/cordova-plugin-remotecommand">Use a Cordova plugin</a>. You get the idea. Basically, Cordova tricks Apple into thinking you’re a native app. And since you’re not a yucky web app, Apple lets you do all the things native apps can do. You just need native tricks – Cordova plugins – to let you do it.</p>
  524. <p>For Google Play, it’s nice that I can just write JS code to make this work; no Cordova plugins required here. Of course, that JS won’t work anywhere except Chrome on Android…but hey, maybe one day (in an ideal world!) all the mobile browsers will implement these web APIs…and the world will live as one. I’m almost ready to bust out some John Lennon hippie utopia tunes.</p>
  525. <p>For Windows Store, do you want to play background audio? Sorry! That is, unless you declare your intentions in our proprietary capabilities manifest file (easy) <em>AND</em> you implement this proprietary media interface using <a href="https://stackoverflow.com/questions/49240479/enabling-background-audio-in-my-windows-store-html5-app?rq=1">window.Windows.SystemMediaTransportControls</a> (not so easy). Otherwise we’ll mute you when your app goes to the background.</p>
  526. <p><strong>Winner</strong>: Google. I want to be able to just write JavaScript, and let the OS pick up cues from my app.</p>
  527. <p><strong>Runner-up</strong>: Windows. I can still write plain old JavaScript, but I need to talk to a proprietary Windows JS API that was injected into my process when running on Windows. Not terrible.</p>
  528. <p><strong>Loser</strong>: Apple. They don’t care about web apps. Actually, it’s worse than that. It feels like they are actually <em>hostile</em> to web apps. iOS Safari is the new Internet Explorer 6. It has lagged behind in nearly every web standard, especially around Progressive Web Apps. This is probably for business reasons: web apps disrupt their $99/year + 33% in-app purchases racket. So to make my web app work on their platform, I have to basically pretend I’m a native app.</p>
  529. <h3>App Store Registration </h3>
  530. <p>Submitting your PWA to the app store requires registration, business verification, and more red tape. Here’s how the 3 app stores fared:</p>
  531. <hr/>
  532. <ul>
  533. <li><strong>Apple</strong>: You must prove that you’re a legal, registered business. This verification isn’t done by us – but <a href="http://www.dnb.com/">by a 3rd party</a>, which may or may not know about your business.<br/> </li>
  534. <li><strong>Google</strong>: You want your app in our store? Cool by us.<br/> </li>
  535. <li><strong>Microsoft</strong>: You want your app in our store? Cool by us.</li>
  536. </ul>
  537. <hr/>
  538. <p>The biggest pain point for me was getting verified as a legal business by Apple.</p>
  539. <p>First, I went to the site and registered for Apple’s Developer Program. I filled out my name and company information. (Aside: I guess Apple won’t let you submit an app unless you have a registered, legal company?)</p>
  540. <p>I click next.</p>
  541. <p>“The information you entered did not match your D&amp;B profile.”</p>
  542. <p>My…what?</p>
  543. <p>A bit of Googling showed that “D&amp;B profile” is Dun and Bradstreet. I’ve never heard of this group before, but I find out that Apple is using them to verify you legal corporation details.</p>
  544. <p>And apparently, my D&amp;B profile didn’t match what I put in my Apple Dev registration.</p>
  545. <p>I google some more and find the Apple dev forums littered with similar posts. Nobody had a good answer.</p>
  546. <p>I contact Apple Dev support. 24 hours later, I’m contacted by email saying that I should contact D&amp;B.</p>
  547. <p><a href="http://debuggerdotbreak.judahgabriel.com/wp-content/uploads/2018/04/image-3.png"><img title="image" alt="image" src="http://debuggerdotbreak.judahgabriel.com/wp-content/uploads/2018/04/image_thumb-1.png" border="0"/></a></p>
  548. <p>I decide to contact them…but Apple says it will take up to a few days for them to respond. </p>
  549. <p>At this point, I’m thinking of abandoning the whole idea.</p>
  550. <p>While waiting for D&amp;B support to get back to me, I decide to go to the D&amp;B site, verify my identity, and update my company information which, I assume, they had taken from government registration records.</p>
  551. <p>Did I mention how sucky this is? I just want to list my existing web app in the store. Plz help.</p>
  552. <p>I go to D&amp;B to update my business profile. Surprise! They have a JavaScript bug in their validation logic that prevents me from updating my profile.</p>
  553. <p>Thankfully, I’m a proficient developer. I click put a breakpoint in their JavaScript, click submit, change the isValid flag to true, and voila! I’ve updated my D&amp;B profile.</p>
  554. <p>Back to Apple Dev –&gt; let’s try this again. Register my company…</p>
  555. <p>“Error: The information you entered did not match your D&amp;B profile.”</p>
  556. <p>AREYOUFREAKINKIDDINGME.</p>
  557. <p>Talk to Apple again. “Oh, it may take 24-48 hours for the updated D&amp;B information to get into our system.”</p>
  558. <p>You know, because digital information can take 2 days to travel from server A to server B. Sigh.</p>
  559. <p>Two days later, I try to register…finally it works! Now I’m in the Apple Developer program and can submit apps for review.</p>
  560. <p><strong>Winner</strong>: Google and Microsoft; both took all of 5 minutes to register. </p>
  561. <p><strong>Loser</strong>: The Apple Developer registration was slow and painful. It took about a week to actually get registered with their developer program. It required me contacting support from 2 different freaking companies. And it required me to <em>runtime debug the JavaScript code on a 3rd party website</em> just so that I can get past their buggy client-side validation, so that my info will flow to Apple, so that I can submit my app to the store. Wow, just…wow.</p>
  562. <p>If there is any saving grace here for Apple, it’s that they have a 501c3 non-profit program, where non-profits can have their $99 annual fee waived. I took advantage of that. And perhaps this extra step complicated matters.</p>
  563. <h3>App Packaging, Building, Submitting</h3>
  564. <p>Once you have a web app, you have to run some magic on it to turn it into something you can submit for App Store review.</p>
  565. <hr/>
  566. <ul>
  567. <li><strong>Apple</strong>: First, buy a Mac; you can’t build an iOS app without a Mac. Install XCode and these build tools and frameworks, acquire a certificate from our developer program, create a profile on a separate website called iTunes Connect, link it up with the certificate you generated on the Apple Dev center, then submit using XCode. Easy as one, two, three…thirty-seven…<br/> </li>
  568. <li><strong>Google</strong>: Download Android Studio, generate a security certificate through it, then package it using the Studio. Upload the package to Android Developer website. <br/> </li>
  569. <li><strong>Microsoft</strong>: Generate an .appx package using these free command line tools, or Visual Studio. Upload to the Microsoft Dev Center website.</li>
  570. </ul>
  571. <hr/>
  572. <p>The good news is, <strong>there’s a free tool to do the magic of turning your web app into app packages</strong>. That awesome free tool is called <a href="https://www.pwabuilder.com">PWABuilder</a>. It analyzes a URL, tells you what you need to do (e.g. maybe add some home screen icons to your PWA web manifest). And in a 3 step wizard, it lets you download packages that contain all the magic:</p>
  573. <ul>
  574. <li>For Windows, it actually generates the .appx package. You can literally take that and submit it on the Windows Dev Center site.<br/> </li>
  575. <li>For Google, it generates a wrapper Java app that contains your PWA web app. From Android Studio, you build this project, which generates the Android package that can be uploaded to the Android Dev Center site.<br/> </li>
  576. <li>For Apple, it generates an XCode project which can be built with XCode. Which requires a Mac.</li>
  577. </ul>
  578. <p>Once again, Apple was the most painful of all of these. I don’t have a Mac. But you cannot build the XCode project for your PWA without a Mac.</p>
  579. <p>I don’t want to pay several thousand dollars to publish my free app in Apple’s app store. I don’t want to pay for the privilege of enriching Apple’s iOS platform.</p>
  580. <p>Thankfully, <a href="http://macincloud.com">MacInCloud</a> costs about $25/month, and they give you a Mac machine with XCode already installed. You can remote into it using Windows Remote Desktop, or even via a web interface. </p>
  581. <p>It wasn’t enough to just build the XCode project and submit. I had to generate a security certificate on the Apple Developer site, then create a new app profile in a separate site, iTunes Connect, where you actually submit the package.</p>
  582. <p>And that wasn’t all: since Apple is hostile to web apps, I had to install some special frameworks and add Cordova plugins that allow my app to do things like to play audio in the background, add the current song to the lock screen, control the song volume and play status from the lock screen, and more.</p>
  583. <p>This took at least a week of finagling to get my app into a working state before I could submit it to the app store.</p>
  584. <p><strong>Winner</strong>: Microsoft. Imagine this: you can go to a website that generates an app package for your web app. And if that’s not your thing, you can download command line tools that will do the job. GUI person? The free Visual Studio will work.</p>
  585. <p><strong>Runner-up</strong>: Google. Requires Android Studio, but it’s free, runs everyone, and was simple enough.</p>
  586. <p><strong>Loser</strong>: Apple. I shouldn’t have to buy a proprietary computer – a several thousand dollar Mac – in order to build my app. The Apple Dev Center –&gt; iTunes Connect tangling seems like an out-of-touch manager’s attempt to push iTunes onto developers. It should simply be part of Apple Developer Center site.</p>
  587. <h3>App Testing</h3>
  588. <p>Once you finally did all the magic incantations to turn your existing web app into a mobile app package, you probably want to send it out to testers before releasing your app on the unwashed masses.</p>
  589. <hr/>
  590. <ul>
  591. <li><strong>Apple</strong>: For testing, you have your testers install Test Flight on their iOS device. Then you add the tester’s email in iTunes Connect. The tester will get a notification and can test your app before it’s available in the app store.<br/> </li>
  592. <li><strong>Google</strong>: In Android Dev Center, you add email addresses of testers. Once added, they can see your alpha/beta version in the App Store.<br/> </li>
  593. <li><strong>Microsoft</strong>: I didn’t actually use this, so I won’t comment on it.</li>
  594. </ul>
  595. <hr/>
  596. <p><strong>Winner</strong>: Toss up. Apple’s Test Flight app is simple and streamlined. You can control alpha/beta expiration simply on the admin side. Google wasn’t far behind; it was quite painless, not even requiring a separate app. </p>
  597. <h3>App Review</h3>
  598. <p>Once your app is ready for prime time, you submit your app for review. The review is done using both a programmatic checklist (e.g. do you have a launch icon?) and by real people (“your app is a clone of X, we reject it”)</p>
  599. <hr/>
  600. <ul>
  601. <li><strong>Apple</strong>: Prior to submission, XCode warns you about potential problems during build. The human app review takes about 24-48 hours.<br/> </li>
  602. <li><strong>Google</strong>: Anybody home? Android Studio didn’t tell me about any potential problems, and my app was approved within minutes of submission. I don’t think a real human being looked at my app.<br/> </li>
  603. <li><strong>Microsoft</strong>: Upon submission, a fast programmatic review caught an issue pertaining to wrong icon formats. After passing, a human reviewed my app within 4 days.</li>
  604. </ul>
  605. <p/><hr/><strong>Winner</strong><p>: Apple.
  606. </p><p>Sure, as a developer, I like the fact that my app was instantly in the Google Play store. But that’s only because, I suspect, it wasn’t actually reviewed by a human. </p>
  607. <p>Apple had the quickest turnaround time for actual human review. Updates also passed review within 24 hours.</p>
  608. <p>Microsoft was hit or miss here. The initial review took 3 or 4 days. An later update took 24 hours. Then another update, where I added XBox platform, took another 3-4 days.</p>
  609. <h3>Conclusion</h3>
  610. <p>It’s painful, and costs money, to take an existing PWA and get them functional on mobile platforms and listed in the App Store.</p>
  611. <p><strong>Winner</strong>: Google. They made it the easiest to get into the app store. The made it the easiest to integrate into the native platform, by attempting to standardize web APIs that OS platforms can pick up on (hello, lovely navigator.mediaSession)</p>
  612. <p><strong>Runner-up</strong>: Microsoft. They made it the easiest to sprinkle your web app with magic, turning it into a package that can be submitted to their store. (Can be done for free using the <a href="http://pwabuilder.com">PWABuilder</a> site!) Integrating with their platform means using the auto-injected window.Windows.</em> JavaScript namespace. Not bad.</p>
  613. <p><strong>Loser</strong>: Apple. Don’t require me to buy a Mac to build an iOS app. Don’t force me to use native wrappers to integrate with your platform. Don’t require me to screw around with security certificates; let your build tools make them for me, and store them automatically in my Dev Center account. Don’t make me use 2 different sites: Apple Dev Center and iTunes Connect.</p>
  614. <p>Final thoughts: The web always wins. It defeated Flash. It killed Silverlight. It destroyed native apps on desktop. The browser is the rich client platform. The OS is merely a browser-launcher and hardware-communicator.</p>
  615. <p>The web will win, too, on mobile. Developers don’t want to build 3 separate apps for the major platforms. Companies don’t want to pay for development of 3 apps.</p>
  616. <p>The answer to all this is the web. We can build rich web apps – Progressive Web Apps – and package them for all the app stores.</p>
  617. <p>Apple in particular has a perverse incentive to stop the progress of the web. It’s the same incentive that Microsoft had in the late ‘90s and early 2000s: it wants to be <em>the </em>platform for good apps. PWAs undermine that; they run everywhere.</p>
  618. <p>My software wisdom is this: PWAs will eventually win and overtake native mobile apps. In 5-10 years, native iOS apps will be as common as Win32 C apps. Apple will go kicking and screaming, keeping iOS Safari behind the curve, blocking PWA progress where they can. (Even their recent “support” for PWAs in iOS Safari 11.1 <a href="https://news.ycombinator.com/item?id=16826852">actually cripple PWAs</a>.)</p>
  619. <p>My suggestion to mobile app platforms is embrace the inevitable and either automatically add quality PWAs to your app store, or allow developers to easily (e.g. free, and with 3 clicks or less) submit a PWA to your store.</p>
  620. <p>Readers, I hope this has been helpful glance at PWAs in App Stores in 2018.</p>
  621. <p>Have you submitted a PWA to an app store? I’d love to hear your experience in the comments.</p></p>
  622. </article>
  623. </section>
  624. <nav id="jumpto">
  625. <p>
  626. <a href="/david/blog/">Accueil du blog</a> |
  627. <a href="http://debuggerdotbreak.judahgabriel.com/2018/04/13/i-built-a-pwa-and-published-it-in-3-app-stores-heres-what-i-learned/">Source originale</a> |
  628. <a href="/david/stream/2019/">Accueil du flux</a>
  629. </p>
  630. </nav>
  631. <footer>
  632. <div>
  633. <img src="/static/david/david-larlet-avatar.jpg" loading="lazy" class="avatar" width="200" height="200">
  634. <p>
  635. Bonjour/Hi!
  636. Je suis <a href="/david/" title="Profil public">David&nbsp;Larlet</a>, je vis actuellement à Montréal et j’alimente cet espace depuis 15 ans. <br>
  637. Si tu as apprécié cette lecture, n’hésite pas à poursuivre ton exploration. Par exemple via les <a href="/david/blog/" title="Expériences bienveillantes">réflexions bimestrielles</a>, la <a href="/david/stream/2019/" title="Pensées (dés)articulées">veille hebdomadaire</a> ou en t’abonnant au <a href="/david/log/" title="S’abonner aux publications via RSS">flux RSS</a> (<a href="/david/blog/2019/flux-rss/" title="Tiens c’est quoi un flux RSS ?">so 2005</a>).
  638. </p>
  639. <p>
  640. Je m’intéresse à la place que je peux avoir dans ce monde. En tant qu’humain, en tant que membre d’une famille et en tant qu’associé d’une coopérative. De temps en temps, je fais aussi des <a href="https://github.com/davidbgk" title="Principalement sur Github mais aussi ailleurs">trucs techniques</a>. Et encore plus rarement, <a href="/david/talks/" title="En ce moment je laisse plutôt la place aux autres">j’en parle</a>.
  641. </p>
  642. <p>
  643. Voici quelques articles choisis :
  644. <a href="/david/blog/2019/faire-equipe/" title="Accéder à l’article complet">Faire équipe</a>,
  645. <a href="/david/blog/2018/bivouac-automnal/" title="Accéder à l’article complet">Bivouac automnal</a>,
  646. <a href="/david/blog/2018/commodite-effondrement/" title="Accéder à l’article complet">Commodité et effondrement</a>,
  647. <a href="/david/blog/2017/donnees-communs/" title="Accéder à l’article complet">Des données aux communs</a>,
  648. <a href="/david/blog/2016/accompagner-enfant/" title="Accéder à l’article complet">Accompagner un enfant</a>,
  649. <a href="/david/blog/2016/senior-developer/" title="Accéder à l’article complet">Senior developer</a>,
  650. <a href="/david/blog/2016/illusion-sociale/" title="Accéder à l’article complet">L’illusion sociale</a>,
  651. <a href="/david/blog/2016/instantane-scopyleft/" title="Accéder à l’article complet">Instantané Scopyleft</a>,
  652. <a href="/david/blog/2016/enseigner-web/" title="Accéder à l’article complet">Enseigner le Web</a>,
  653. <a href="/david/blog/2016/simplicite-defaut/" title="Accéder à l’article complet">Simplicité par défaut</a>,
  654. <a href="/david/blog/2016/minimalisme-esthetique/" title="Accéder à l’article complet">Minimalisme et esthétique</a>,
  655. <a href="/david/blog/2014/un-web-omni-present/" title="Accéder à l’article complet">Un web omni-présent</a>,
  656. <a href="/david/blog/2014/manifeste-developpeur/" title="Accéder à l’article complet">Manifeste de développeur</a>,
  657. <a href="/david/blog/2013/confort-convivialite/" title="Accéder à l’article complet">Confort et convivialité</a>,
  658. <a href="/david/blog/2013/testament-numerique/" title="Accéder à l’article complet">Testament numérique</a>,
  659. et <a href="/david/blog/" title="Accéder aux archives">bien d’autres…</a>
  660. </p>
  661. <p>
  662. On peut <a href="mailto:david%40larlet.fr" title="Envoyer un courriel">échanger par courriel</a>. Si éventuellement tu souhaites que l’on travaille ensemble, tu devrais commencer par consulter le <a href="http://larlet.com">profil dédié à mon activité professionnelle</a> et/ou contacter directement <a href="http://scopyleft.fr/">scopyleft</a>, la <abbr title="Société coopérative et participative">SCOP</abbr> dont je fais partie depuis six ans. Je recommande au préalable de lire <a href="/david/blog/2018/cout-site/" title="Attention ce qui va suivre peut vous choquer">combien coûte un site</a> et pourquoi je suis plutôt favorable à une <a href="/david/pro/devis/" title="Discutons-en !">non-demande de devis</a>.
  663. </p>
  664. <p>
  665. Je ne traque pas ta navigation mais mon
  666. <abbr title="Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33.184162340">hébergeur</abbr>
  667. conserve des logs d’accès.
  668. </p>
  669. </div>
  670. </footer>
  671. <script type="text/javascript">
  672. ;(_ => {
  673. const jumper = document.getElementById('jumper')
  674. jumper.addEventListener('click', e => {
  675. e.preventDefault()
  676. const anchor = e.target.getAttribute('href')
  677. const targetEl = document.getElementById(anchor.substring(1))
  678. targetEl.scrollIntoView({behavior: 'smooth'})
  679. })
  680. })()
  681. </script>