Skip to content

Commit 2e4b229

Browse files
authored
Merge pull request #37 from line/support-mini-features
MINI App Features support
2 parents bba906b + 30c9e5e commit 2e4b229

16 files changed

+717
-571
lines changed

.env

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
VITE_LIFF_ID=1656508316-k7jNojXm
1+
VITE_LIFF_ID=2006142821-boxjqY7m
2+
VITE_LIFF_ID_MINI=2006593963-DPoAqdzG
3+
VITE_LIFF_ID_MINI_PREVIEW=2006593962-KMmZJVxl

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"lint": "eslint \"src/**/*.{ts,tsx}\""
1616
},
1717
"dependencies": {
18-
"@line/liff": "2.23.1",
18+
"@line/liff": "2.25.1",
19+
"@line/liff-common-profile-plugin": "0.1.0",
1920
"react": "^17.0.2",
2021
"react-dom": "^17.0.2"
2122
},
File renamed without changes.

public/assets/qr_mini.png

16.8 KB
Loading

public/assets/qr_mini_preview.png

17.1 KB
Loading

src/App.module.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ body {
2121
height: auto;
2222
}
2323

24+
.applicationNotice {
25+
color: #777777;
26+
font-size: 15px;
27+
}
28+
2429
@media only screen and (max-width: 375px) {
2530
.container {
2631
width: auto;

src/App.tsx

Lines changed: 82 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,37 @@
1-
import React from 'react'
1+
import React, { useContext } from 'react'
22
import liff from '@line/liff'
33
import styles from './App.module.css'
44
import Header from './components/Header'
55
import Snippet from './components/Snippet'
66
import Input from './components/Input'
7-
import { FilterContext, FilterTypes } from './Context'
8-
import qrCode from './qr-code.png'
9-
import { SHARE_TARGET_PICKER_FIXED_ARGUMENT_LIST } from './constants'
10-
11-
const isMINI = new URLSearchParams(location.search).has('mini')
12-
const filter = isMINI ? FilterTypes.MINI : FilterTypes.LIFF
7+
import { SHARE_TARGET_PICKER_FIXED_ARGUMENT_LIST, QR_IMG_MAP } from './constants'
8+
import { FilterTypes } from './FilterTypes'
9+
import { AppContext } from './Context'
1310

1411
function App() {
12+
const { appUrl, filter } = useContext(AppContext)
13+
1514
let isLoggedIn = false
1615
try {
1716
isLoggedIn = liff.isLoggedIn()
1817
} catch (e) {
1918
console.log(e)
2019
}
20+
2121
return (
22-
<FilterContext.Provider value={filter}>
22+
<>
2323
<Header />
2424
<div className={styles.container}>
25+
{filter === FilterTypes.MINI || filter === FilterTypes.MINI_PREVIEW ? (
26+
<div className={styles.applicationNotice}>
27+
本「LINEミニアプリプレイグラウンド」は日本限定のサービスです。
28+
<br />
29+
This “LINE MINI App Playground” is available only in Japan.
30+
</div>
31+
) : null}
2532
<div className={styles.liffIdBox}>
26-
<Input
27-
readonly
28-
value={`LIFF URL: https://liff.line.me/${import.meta.env.VITE_LIFF_ID.toString()}`}
29-
/>
30-
<img src={qrCode} className={styles.qrCode} />
33+
<Input readonly value={`URL: ${appUrl}`} />
34+
<img src={QR_IMG_MAP[filter]} className={styles.qrCode} />
3135
</div>
3236
<h1>Client APIs</h1>
3337
{!isLoggedIn ? (
@@ -226,7 +230,9 @@ function App() {
226230
docUrl="https://developers.line.biz/en/reference/liff/#share-target-picker"
227231
needRequestPayload={true}
228232
hideResponse={true}
229-
defaultRequestPayload={SHARE_TARGET_PICKER_FIXED_ARGUMENT_LIST[0].value}
233+
defaultRequestPayload={
234+
SHARE_TARGET_PICKER_FIXED_ARGUMENT_LIST[0].value
235+
}
230236
pulldownOptions={SHARE_TARGET_PICKER_FIXED_ARGUMENT_LIST}
231237
skipAutoRun={true}
232238
runner={async (options) => {
@@ -291,22 +297,70 @@ function App() {
291297
return await liff.permanentLink.createUrlBy(url)
292298
}}
293299
/>
294-
<Snippet
295-
apiName="liff.i18n.setLang"
296-
version="2.21.0"
297-
docUrl="https://developers.line.biz/ja/reference/liff/#i18n-set-lang"
298-
needRequestPayload={true}
299-
skipAutoRun={true}
300-
hideResponse={true}
301-
defaultRequestPayload={'en'}
302-
runner={async (lang) => {
303-
return await liff.i18n.setLang(lang)
304-
}}
305-
/>
300+
{(filter === FilterTypes.MINI ||
301+
filter === FilterTypes.MINI_PREVIEW) && (
302+
<>
303+
<Snippet
304+
apiName="liff.createShortcutOnHomeScreen"
305+
version="2.23.0"
306+
docUrl="https://developers.line.biz/en/reference/liff/#create-shortcut-on-home-screen"
307+
needRequestPayload={true}
308+
defaultRequestPayload={JSON.stringify(
309+
{
310+
url: appUrl,
311+
},
312+
null,
313+
4
314+
)}
315+
runner={async (payload) => {
316+
const parsed = JSON.parse(payload)
317+
await liff.createShortcutOnHomeScreen(parsed)
318+
}}
319+
skipAutoRun={true}
320+
isInLIFF={false}
321+
/>
322+
<Snippet
323+
apiName="liff.$commonProfile.getDummy"
324+
version="2.19.0"
325+
docUrl="https://developers.line.biz/en/docs/partner-docs/quick-fill/overview/"
326+
needRequestPayload={true}
327+
defaultRequestPayload={JSON.stringify(
328+
[
329+
[
330+
'family-name',
331+
'given-name',
332+
'family-name-kana',
333+
'given-name-kana',
334+
'sex-enum',
335+
'bday-year',
336+
'bday-month',
337+
'bday-day',
338+
'tel',
339+
'email',
340+
'postal-code',
341+
'address-level1',
342+
'address-level2',
343+
'address-level3',
344+
'address-level4',
345+
],
346+
1,
347+
],
348+
null,
349+
4
350+
)}
351+
runner={async (p) => {
352+
const payload = JSON.parse(p)
353+
return await liff.$commonProfile.getDummy(...payload)
354+
}}
355+
inClientOnly={true}
356+
skipAutoRun={true}
357+
isInLIFF={false}
358+
/>
359+
</>
360+
)}
306361
</div>
307-
</FilterContext.Provider>
362+
</>
308363
)
309364
}
310365

311-
312366
export default App

src/Context.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import React from 'react'
2-
3-
export const FilterTypes = {
4-
LIFF: 'LIFF',
5-
MINI: 'MINI'
6-
} as const
2+
import { FilterTypes } from './FilterTypes'
73

84
export type FilterType = keyof typeof FilterTypes
95

10-
export const FilterContext = React.createContext<FilterType>(FilterTypes.LIFF)
6+
export const AppContext = React.createContext<{
7+
filter: FilterType,
8+
appId: string,
9+
appUrl: string
10+
}>({
11+
filter: FilterTypes.LIFF,
12+
appId: import.meta.env.VITE_LIFF_ID,
13+
appUrl: `https://liff.line.me/${import.meta.env.VITE_LIFF_ID}`
14+
})

src/FilterTypes.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const FilterTypes = {
2+
LIFF: 'LIFF',
3+
MINI: 'MINI',
4+
MINI_PREVIEW: 'MINI_PREVIEW'
5+
} as const

src/components/Header.module.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
align-items: center;
1818
}
1919

20+
.left {
21+
flex: 1;
22+
}
23+
2024
.left a {
2125
text-decoration: none;
2226
color: #000;
@@ -41,4 +45,7 @@
4145
.gitHubButton {
4246
display: none;
4347
}
48+
.right {
49+
flex-basis: 100px;
50+
}
4451
}

src/components/Header.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
import liff from '@line/liff'
2-
import React from 'react'
2+
import React, { useContext, useMemo } from 'react'
33
import styles from './Header.module.css'
44
import Button from './Button'
5+
import { AppContext } from '../Context'
56

67
export default function Header() {
8+
const {filter, appId} = useContext(AppContext);
9+
const appName = useMemo(() => {
10+
return filter === 'LIFF' ? 'LIFF Playground' : 'LINE MINI App Playground'
11+
}, [filter])
12+
713
const openGitHub = () => {
814
window.open(`https://github.com/line/liff-playground`, '_blank')
915
}
1016

1117
const openInApp = () => {
1218
window.open(
13-
`https://line.me/R/app/${import.meta.env.VITE_LIFF_ID}`,
19+
`https://line.me/R/app/${appId}`,
1420
'_blank'
1521
)
1622
}
@@ -20,7 +26,7 @@ export default function Header() {
2026
<div className={styles.header}>
2127
<div className={styles.left}>
2228
<a href='/'>
23-
<h1>LIFF Playground</h1>
29+
<h1>{appName}</h1>
2430
</a>
2531
</div>
2632
<div className={styles.right}>

src/components/Snippet.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import Input from './Input'
44
import styles from './Snippet.module.css'
55
import Tag from './Tag'
66
import TextArea from './TextArea'
7-
import { FilterContext, FilterTypes } from '../Context'
7+
import { AppContext } from '../Context'
8+
import { FilterTypes } from '../FilterTypes'
89
import Pulldown from './Pulldown'
910

1011
interface SippetProps {
@@ -73,8 +74,8 @@ export default function Snippet({
7374
}, [skipAutoRun, callRunner])
7475

7576
return (
76-
<FilterContext.Consumer>
77-
{(filter) =>
77+
<AppContext.Consumer>
78+
{({ filter }) =>
7879
((filter === FilterTypes.LIFF && isInLIFF) ||
7980
(filter === FilterTypes.MINI && isInMINI)) && (
8081
<div className={styles.snippet}>
@@ -153,6 +154,6 @@ export default function Snippet({
153154
</div>
154155
)
155156
}
156-
</FilterContext.Consumer>
157+
</AppContext.Consumer>
157158
)
158159
}

src/constants.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
const base = new URL(location.href).origin;
1+
import { FilterTypes } from "./FilterTypes"
2+
3+
const base = new URL(location.href).origin
24
export const SHARE_TARGET_PICKER_FIXED_ARGUMENT_LIST = [
35
{
46
label: 'text',
@@ -54,3 +56,9 @@ export const SHARE_TARGET_PICKER_FIXED_ARGUMENT_LIST = [
5456
label,
5557
value: JSON.stringify(value, null, 4),
5658
}))
59+
60+
export const QR_IMG_MAP = {
61+
[FilterTypes.LIFF]: `${base}/assets/qr_liff.png`,
62+
[FilterTypes.MINI]: `${base}/assets/qr_mini.png`,
63+
[FilterTypes.MINI_PREVIEW]: `${base}/assets/qr_mini_preview.png`,
64+
}

src/main.tsx

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,48 @@
11
import liff from '@line/liff'
22
import React from 'react'
33
import ReactDOM from 'react-dom'
4+
import { LiffCommonProfilePlugin } from '@line/liff-common-profile-plugin'
45
import './main.css'
56
import App from './App'
7+
import { FilterTypes } from './FilterTypes'
8+
import { AppContext } from './Context'
9+
10+
const isMINI = new URLSearchParams(location.search).has('mini')
11+
const isPreviewMINI = new URLSearchParams(location.search).has('mini_preview')
12+
13+
const filter = isPreviewMINI
14+
? FilterTypes.MINI_PREVIEW
15+
: isMINI
16+
? FilterTypes.MINI
17+
: FilterTypes.LIFF
18+
19+
const appId = {
20+
[FilterTypes.LIFF]: import.meta.env.VITE_LIFF_ID,
21+
[FilterTypes.MINI]: import.meta.env.VITE_LIFF_ID_MINI,
22+
[FilterTypes.MINI_PREVIEW]: import.meta.env.VITE_LIFF_ID_MINI_PREVIEW
23+
}[filter]
24+
25+
const appUrl =
26+
filter === FilterTypes.LIFF
27+
? `https://liff.line.me/${appId}`
28+
: `https://miniapp.line.me/${appId}`
29+
30+
const injectPlugins = () => {
31+
liff.use(new LiffCommonProfilePlugin())
32+
}
33+
34+
if (filter === FilterTypes.MINI || filter === FilterTypes.MINI_PREVIEW) {
35+
injectPlugins()
36+
}
637

738
liff
8-
.init({ liffId: import.meta.env.VITE_LIFF_ID || '' })
39+
.init({ liffId: appId })
940
.then(() => {
1041
ReactDOM.render(
1142
<React.StrictMode>
12-
<App />
43+
<AppContext.Provider value={{ filter, appId, appUrl }}>
44+
<App />
45+
</AppContext.Provider>
1346
</React.StrictMode>,
1447
document.getElementById('root')
1548
)

src/vite-env.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
interface ImportMetaEnv {
44
readonly VITE_LIFF_ID: string;
5+
readonly VITE_LIFF_ID_MINI: string;
6+
readonly VITE_LIFF_ID_MINI_PREVIEW: string;
57
}
68

79
interface ImportMeta {

0 commit comments

Comments
 (0)