Post

웹 사이트 바로가기

바로가기 기능 개발

웹 사이트 바로가기 생성

바탕화면에 웹 바로가기 생성하는 기능에 대해 설명해보겠습니다.

북마크를 사용하면 되는 거 아닌가요? 저도 그렇게 생각했지만 생각보다 브라우저에 능숙하지 않은 분들의 cs가 꽤 들어오더군요. 그래서 사용자의 편의성을 위해 바로가기 기능을 적용했습니다. 여러가지 방법이 있겠지만 여기서 설명할 방법은 2가지입니다. 첫 번째 크롬의 앱 설치 기능, 두 번째 자바를 이용한 바로가기 생성이 되겠습니다. 그럼 시작!

1. 매니패스트를 사용한 바로가기 생성

사이트 바로가기 생성이지만 정확히 설명하면 사이트를 앱 화 시켜서 앱을 설치하는 기능입니다. 크롬 기반이지만 엣지에서도 사용 가능하고 무엇보다 코드가 간단하다는 장점이 있습니다.

url: chrome://apps

데스크탑, 작업표시줄 바로가기 생성 기능

1.1. 필수 소스

  • manifest.json
  • sw.js
  • .js

개발할 때 확인해야 하는 목록은 매니페스트와 Service workers 입니다.

매니페스트.json은 설정 값이 제대로 되어 있는 지 확인할 수 있고, Service workers는 sw.js가 동작하는지 확인할 수 있습니다.

확인 방법: 사이트 F12(개발자도구) → 애플리케이션 → 매니페스트 , Service workers

img

참고 사이트 (크롬에서 제공하는 공식 문서)

https://web.dev/articles/add-manifest?utm_source=devtools&hl=ko

1) manifest.json

웹 앱 매니페스트는 브라우저에 프로그레시브 웹 앱(PWA)이 사용자의 데스크톱 또는 휴대기기에 설치될 때 어떻게 동작해야 하는지 알려주는 JSON 파일입니다. 확장자는 .webmanifest 또는 JSON으로 하면 더 명확히 읽을 수 있습니다.

필수 파라미터 형식 (최소 형식)

최소한의 기능을 담은 JSON입니다. 기본 설정에는 shortcuts, screenshots 등 여러 파라미터값이 많지만 여기서는 최소화 시킨 코드로 설명을 진행하겠습니다. 만약 기본 코드로 사용하고 싶다면 주의해야 할 점은 기본 파라미터들 중에 배열인 파라미터가 있다는 것 입니다.

  • 배열: icons , shortcuts,screenshots

최소화 시킨 manifest.json

  1. name: 앱이 설치될 때 사용될 이름
  2. short_name: 사용자의 홈 화면, 런처 이름
    • name과 short_name 둘 중 하나만 적어도 됨
  3. start_url: 사이트 다운로드 위치에 배치를 해야 함
    • 설치를 진행할 해당 페이지의 api를 적어야 한다.
    • ex) 기본 홈 → ‘/’ , 쇼핑 페이지 → ‘/shop’
  4. icons:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
  "name": "앱이 설치될 때 사용될 이름",
  "short_name": "사용자의 홈 화면, 런처 이름",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#000000",
  "icons": [
    {
      "src": "/resources/js/main/manifest/파일명.png",
      "type": "image/png",
      "sizes": "192x192"
    },
    {
      "src": "/resources/js/main/manifest/파일명.png",
      "type": "image/png",
      "sizes": "192x192"  // 기본사이즈 (192 or 512)
    }
  ]
}

공식에서 제공하는 기본 manifest.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
{
  "short_name": "Weather",
  "name": "Weather: Do I need an umbrella?",
  "icons": [
    {
      "src": "/images/icons-vector.svg",
      "type": "image/svg+xml",
      "sizes": "512x512"
    },
    {
      "src": "/images/icons-192.png",
      "type": "image/png",
      "sizes": "192x192"
    },
    {
      "src": "/images/icons-512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ],
  "id": "/?source=pwa",
  "start_url": "/?source=pwa",
  "background_color": "#3367D6",
  "display": "standalone",
  "scope": "/",
  "theme_color": "#3367D6",
  "shortcuts": [
    {
      "name": "How's the weather today?",
      "short_name": "Today",
      "description": "View weather information for today",
      "url": "/today?source=pwa",
      "icons": [{ "src": "/images/today.png", "sizes": "192x192" }]
    },
    {
      "name": "How's the weather tomorrow?",
      "short_name": "Tomorrow",
      "description": "View weather information for tomorrow",
      "url": "/tomorrow?source=pwa",
      "icons": [{ "src": "/images/tomorrow.png", "sizes": "192x192" }]
    }
  ],
  "description": "Weather forecast information",
  "screenshots": [
    {
      "src": "/images/screenshot1.png",
      "type": "image/png",
      "sizes": "540x720",
      "form_factor": "narrow"
    },
    {
      "src": "/images/screenshot2.jpg",
      "type": "image/jpg",
      "sizes": "720x540",
      "form_factor": "wide"
    }
  ]
}

2) js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
(function () {
		if ("serviceWorker" in navigator) {
			navigator.serviceWorker.register('/js/sw.js').then(function (registration) {
				console.log(registration);
			}, function (err) {
				console.log(err)
			});
		}
	})();

	let deferredPrompt;
	document.getElementById('shortcut_btn').addEventListener('click', () => {
		console.log(deferredPrompt);
		if (deferredPrompt) {
			deferredPrompt.prompt();

			deferredPrompt.userChoice.then((choiceResult) => {
				if (choiceResult.outcome === 'accepted') {
					console.log('Accept');
				} else {
					console.log('Dismiss');
				}
				deferredPrompt = null;
			});
		}
	});

	window.addEventListener('beforeinstallprompt', (event) => {
		event.preventDefault();
		deferredPrompt = event;
		console.log("beforeinstallprompt");
	});

3) sw.js

  • 혼자서 계속 작동
  • chrome에서 지원하는 애플리케이션 기술
  • 개발자 도구 → 애플리케이션 → 매니페스트, service workers 에서 확인 가능
  • 매니페스트가 없으면 감지된 매니페스트가 없다고 뜸.
1
2
3
4
5
6
7
8
9
10
11
1. 기본 코드 
self.addEventListener("fetch", e => {});

2. 디버깅 추가한 코드 
self.addEventListener('install', (event) => {
    console.log('Service Worker: Installed');
});

self.addEventListener('fetch', (event) => {
    console.log('Service Worker: Fetching');
}); 
  • deferredPrompt undefined로 인식되는 경우
    • json에 있는 객체 형식이 맞지 않을 경우
    • 이미 chrome://apps에 바로가기가 설치되어 있는 경우

img

주의할 점

  1. 매니페스트가 동작하려면 manifest.json을 생성하여 기본 정보를 입력해야 한다. (필수)
  2. 기본적으로 바로가기는 현재 도메인을 기준으로 생성하기 때문에 start_url에 현재 url을 입력해야 한다. (절대적임)
  3. webapp → static에 있는 코드를 읽어오기 때문에 icons의 파일 경로는 resources 아래에 두어야 하고, 절대 경로로 작성해야 한다. (상대 경로 인식 못함)
  4. 사이트에 들어가자 마자 static에 있는 코드를 읽어서 실행하기 때문에 JSON 정보를 임의로 변경하여 바로가기 생성을 하지 못함.
  5. 이미지는 최소 사이즈가 144px 이상이어야 함.
    • 참고
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// manifest.json 정보 셋팅
function setManifest(result) {
    try {
        // 기존 manifest 제거
        const existingLink = document.querySelector('link[rel="manifest"]');
        if (existingLink) {
            existingLink.parentNode.removeChild(existingLink);
        }

        // 새로운 manifest 추가
        const manifestLink = document.createElement('link');
        manifestLink.rel = 'manifest';

        // JSON 객체를 Blob으로 변환
        const jsonObject = JSON.stringify(result);
        const manifestBlob = new Blob([jsonObject], { type: 'application/json' });
        const manifestUrl = URL.createObjectURL(manifestBlob);

        // Blob URL을 href로 설정
        manifestLink.href = manifestUrl;

        // <head>에 추가
        document.head.appendChild(manifestLink);

        // console.log("생성된 Manifest URL:", manifestUrl);
        // console.log("동적 manifest 설정 완료");
    } catch (e) {
        console.error("Manifest 설정 중 오류 발생:", e);
    }
}


2. 자바 ActiveXShell을 사용한 바로가기 생성

  • 이미지 경로는 사용자의 로컬이어야 한다.
  • 이미지 확장자: .ico
  • 백슬래시 주의

2.1 VBScript 파일 내용 설정

  1. Set oWs = “”;
  2. WScript.CreateObject(WScript.Shell): WScript.Shell 생성
  3. sDestop: 바탕화면 경로
  4. sLinkFile: 바로가기 설치 경로
  5. Set oLink = oWs.CreateShortcut(sLinkFile): oLink에 바로가기 설치 경로 설정
  6. oLink.TargetPath: 바로가기 경로
  7. oLink.WorkingDirectiory: 작업 디렉토리
  8. oLink.WindowStyle = 1 (1: 일반창, 2: 큰 창, 3: 작은 창)
  9. oLink.Description: 바로가기 이름 설정
  10. oLink.IconLocation: 아이콘 이미지 경로 (로컬파일시스템 절대경로여야 함)
  11. oLink.save: 저장
  12. VBScript 파일 생성
    1. new File("createShortcutWithIcon.vbs"); : 바로가기 아이콘 생성
    2. BufferedWriter writer = new BufferedWriter(new FileWriter(scriptFile)); : script 넣음
    3. writer.write(vbScriptContent); : 1~11번까지 스크립트 String 변수에 넣음
    4. writer.close();
  13. VBScript 실행
    1. Runtime.*getRuntime*().exec("wscript createShortcutWithIcon.vbs").waitFor();
  14. VBScript 파일 삭제
    1. scriptFile.delete();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
 try {
      String serverIconUrl = "http://localhost:8080/common/images/icon.png";
      String localIconPath = System.getProperty("user.home") + "\\desktop\\test.ico";
      logger.info("localIconPath = " + localIconPath);
      // 아이콘 파일 다운로드
      downloadFileFromServer(serverIconUrl, localIconPath);

      // VBScript 파일 내용 설정
      logger.info("자바바로가기");
      String vbScriptContent =
              "Set oWS = WScript.CreateObject(\"WScript.Shell\")\n" +
                      "sDesktop = oWS.SpecialFolders(\"Desktop\")\n" + 
                      "sLinkFile = sDesktop & \"\\테스트.lnk\"\n" +
                      "Set oLink = oWS.CreateShortcut(sLinkFile)\n" +
                      "oLink.TargetPath = \"https://www.test.co.kr\"\n" + 
                      "oLink.WorkingDirectory = \"C:\\\\Program Files\\\\Example\"\n" + 
                      "oLink.WindowStyle = 1\n" + // 1: 일반 창
                      "oLink.Description = \"테스트 바로가기\"\n" + 
                      "oLink.IconLocation = \"" + localIconPath + "\"\n"  +
                      "oLink.Save";

      // VBScript 파일 생성
      File scriptFile = new File("createShortcutWithIcon.vbs");
      BufferedWriter writer = new BufferedWriter(new FileWriter(scriptFile));
      writer.write(vbScriptContent);
      writer.close();

      // VBScript 실행
      Runtime.getRuntime().exec("wscript createShortcutWithIcon.vbs").waitFor();

      // VBScript 파일 삭제
      scriptFile.delete();
      logger.info("아이콘이 포함된 바로가기가 성공적으로 생성되었습니다!");
      return new ResponseEntity<>(HttpStatus.OK);

  } catch (IOException | InterruptedException e) {
      e.printStackTrace();
  }

  
// 서버에서 파일 다운로드 메서드
private static void downloadFileFromServer(String fileURL, String savePath) throws IOException {
    URL url = new URL(fileURL);
    HttpURLConnection connection = (HttpURLConnection) url.openConnection();

    try (InputStream inputStream = connection.getInputStream();
         FileOutputStream outputStream = new FileOutputStream(savePath)) {
        byte[] buffer = new byte[4096];
        int bytesRead;
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, bytesRead);
        }
    }
    System.out.println("아이콘 다운로드 완료: " + savePath);
}

3. 결론

1. 매니페스트

장점

  • 간단한 코드로 바로가기 생성 가능
  • 바로가기 세분화 설정 가능 (이미지, 내용, 제목 … )
  • app으로 저장되기 때문에 바로가기, 작업표시줄 저장 같은 다양한 설정이 가능함.

단점

  • 한번 설정한 json 설정은 임의로 변경 불가능

    예) 도메인에 따라 바로가기 설정을 다르게 하고 싶다 → 불가능함

  • 로컬 앱(태블릿, 핸드폰)에서 테스트 진행을 하기 위해서는 승인된 SSL이 필요함

    → 앱에서 작동이 안되더라도 사이트에서 작동 되면 앱에서도 작동됨 90% 확률

2. ActiveXShell

장점

  • 브라우저 제약 없이 사용 가능
  • 파일로 생성되기 때문에 매니페스트 같이 app화 되지 않음
  • 특정 변경 값에 따른 바로가기 설정도 변경이 가능함

    예) 파라미터에 따라 다른 값 설정 가능

단점

  • 이미지를 설정하기 위해서는 해당 이미지를 유저의 컴퓨터에 설치해야 함
This post is licensed under CC BY 4.0 by the author.