TEAMS 채팅방 생성 및 메시지 발송까지

우선 시작하기에 앞서서 TEAMS의 graph api는 application의 채팅방 생성(Create chat)을 지원하지 않습니다. 이를 해결하기 위해서 진행한 내용을 정리했습니다.

1. 채팅방 생성을 하고, 메시지를 발송하는 역할의 계정을 생성해야 합니다.

계정의 Access token을 이용해서 채팅방을 생성하고 메시지 발송을 하는 teams graph api를 호출합니다.

2. 계정 생성이 완료 되었으면, 채팅 관련 권한을 할당합니다.

권한 할당은 graph-explorer를 통해서 쉽게 할 수 있습니다.

3. 사용자의 Access token을 저장하는 프로그램을 작성합니다.

시작에도 이야기 했듯이 Teams graph api의 Create chat기능은 Delegated를 통해서만 사용이 가능합니다.

| Permission type | Permissions (from least to most privileged) |
|:--------|:-------:|--------:|
| Delegated (work or school account) | Chat.Create, Chat.ReadWrite |
|----
| Delegated (personal Microsoft account) | Not supported. |
|----
| Application | Not supported. |
{: rules="groups"}

따라서 사용자의 Access token을 받아오는 프로그램이 필요합니다.

여기에는 2가지 옵션이 있습니다.

  • app 을 생성해서 등록 후 Access token을 생성하는 방법
  • graph-explorer를 통해서 Access token을 획득하는 방법

기본적으로 사용자의 로그인 행위(위임) 없이는 원하는 Access token 생성이 불가능 합니다.

제 경우는 puppeteergraph-explorer 서비스를 활용해 Access token을 서버에 저장 후 사용했습니다.

Access token은 1시간 동안 유효합니다.

매 50분마다 Access token 재할당 받아 서버에 저장하도록 처리했습니다.

4. 채팅방 생성

채팅방을 생성 할때 가장 중요한 옵션은 그룹 대화방일대일 대화방에 대한 결정입니다.

  • 일대일 대화방은 다른 사용자를 초대하지 못합니다.
  • 그룹 대화방의 경우 다른 사용자를 초대 / 제외가 가능합니다. 다만, graph api 를 이용해서 채팅방을 생성시 이전 대화목록을 보여주는 옵션을 현재까진 지원하지 않습니다.

기본 형식

POST /chats

요청 예제

POST https://graph.microsoft.com/v1.0/chats
Content-Type: application/json

{
  "chatType": "oneOnOne", // 그룹일 경우 'group'로 설정해야합니다.
  "members": [
    {
      "@odata.type": "#microsoft.graph.aadUserConversationMember",
      "roles": ["owner"],
      "user@odata.bind": "https://graph.microsoft.com/v1.0/users('kk.lim@gsretail.com')"
    },
    {
      "@odata.type": "#microsoft.graph.aadUserConversationMember",
      "roles": ["owner"],
      "user@odata.bind": "https://graph.microsoft.com/v1.0/users('82af01c5-f7cc-4a2e-a728-3a5df21afd9d')"
    }
  ]
}

응답 예제

HTTP/1.1 201 Created
Content-Type: application/json

{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#chats/$entity",
    "id": "19:82fe7758-5bb3-4f0d-a43f-e555fd399c6f_8c0a1a67-50ce-4114-bb6c-da9c5dbcf6ca@unq.gbl.spaces",
    "topic": null,
    "createdDateTime": "2020-12-04T23:10:28.51Z",
    "lastUpdatedDateTime": "2020-12-04T23:10:28.51Z",
    "chatType": "oneOnOne"
}

일대일 채팅방은 다시 채팅방을 생성하여도 동일한 id로 응답합니다.

id 값은 메시지를 전송하거나, 다른 사람을 초대 / 제외 시 사용됩니다.

아래 예제 부터는 그룹 채팅방을 생성하고 19:2da4c29f6d7041eca70b638b43d45437@thread.v2을 응답 id로 받았을 경우입니다.

5. 메시지 전송 처리

메세지 전송을 위해서는 아래와 같은 권한이 필요합니다.

| Permission type | Permissions (from least to most privileged) |
|:--------|:-------:|--------:|
| Delegated (work or school account) | ChatMessage.Send, Chat.ReadWrite |
|----
| Delegated (personal Microsoft account) | Not supported. |
|----
| Application | Not supported. |
{: rules="groups"}

기본 형식

POST /chats/{chat-id}/messages

요청 예제

POST https://graph.microsoft.com/v1.0/chats/19:2da4c29f6d7041eca70b638b43d45437@thread.v2/messages
Content-type: application/json

{
  "body": {
     "content": "Hello world",
     "contentType": "text" // 기본 값은 text 이며, "html"으로 설정하면 html 형식으로 전송이 가능합니다.
  }
}

응답 예제

HTTP/1.1 201 Created
Content-type: application/json

{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#chats('19%3A2da4c29f6d7041eca70b638b43d45437%40thread.v2')/messages/$entity",
    "id": "1616991463150",
    "replyToId": null,
    "etag": "1616991463150",
    "messageType": "message",
    "createdDateTime": "2021-03-29T04:17:43.15Z",
    "lastModifiedDateTime": "2021-03-29T04:17:43.15Z",
    "lastEditedDateTime": null,
    "deletedDateTime": null,
    "subject": null,
    "summary": null,
    "chatId": "19:2da4c29f6d7041eca70b638b43d45437@thread.v2",
    "importance": "normal",
    "locale": "en-us",
    "webUrl": null,
    "channelIdentity": null,
    "policyViolation": null,
    "from": {
        "application": null,
        "device": null,
        "conversation": null,
        "user": {
            "id": "8ea0e38b-efb3-4757-924a-5f94061cf8c2",
            "displayName": "Robin Kline",
            "userIdentityType": "aadUser"
        }
    },
    "body": {
        "contentType": "text",
        "content": "Hello World"
    },
    "attachments": [],
    "mentions": [],
    "reactions": []
}

6. 멤버 초대 처리

기본 형식

POST /chats/{chat-id}/members

요청 예제

POST https://graph.microsoft.com/v1.0/chats/19:cf66807577b149cca1b7af0c32eec122@thread.v2/members
content-type: application/json

{
    "@odata.type": "#microsoft.graph.aadUserConversationMember",
    "user@odata.bind": "https://graph.microsoft.com/v1.0/users/8b081ef6-4792-4def-b2c9-c363a1bf41d5",
    "visibleHistoryStartDateTime": "2019-04-18T23:51:43.255Z",
    "roles": ["owner"]
}

응답 예제

HTTP/1.1 201 Created
Location: /chats/19:cf66807577b149cca1b7af0c32eec122@thread.v2/members/MCMjMjQzMmI1N2ItMGFiZC00M2RiLWFhN2ItMTZlYWRkMTE1ZDM0IyMxOTpiZDlkYTQ2MzIzYWY0MjUzOTZkMGZhNjcyMDAyODk4NEB0aHJlYWQudjIjIzQ4YmY5ZDUyLWRjYTctNGE1Zi04Mzk4LTM3Yjk1Y2M3YmQ4Mw==

7. 멤버 제외 처리

멤버 제외의 경우 채팅방의 멤버 id를 조회 하고 해당 id를 DELETE 호출을 해야 합니다.

7.1 멤버 조회

기본 형식

GET /chats/{chat-id}/members
GET /users/{user-id | user-principal-name}/chats/{chat-id}/members

요청 예제

GET https://graph.microsoft.com/v1.0/me/chats/19:8b081ef6-4792-4def-b2c9-c363a1bf41d5_5031bb31-22c0-4f6f-9f73-91d34ab2b32d@unq.gbl.spaces/members

응답 예제

HTTP/1.1 200 OK
Content-type: application/json

{
   "@odata.context":"https://graph.microsoft.com/v1.0/$metadata#users('8b081ef6-4792-4def-b2c9-c363a1bf41d5')/chats('19%3A8b081ef6-4792-4def-b2c9-c363a1bf41d5_5031bb31-22c0-4f6f-9f73-91d34ab2b32d%40unq.gbl.spaces')/members",
   "@odata.count":3,
   "value":[
      {
         "@odata.type":"#microsoft.graph.aadUserConversationMember",
         "id":"8b081ef6-4792-4def-b2c9-c363a1bf41d5",
         "roles":[
            "owner"
         ],
         "displayName":"John Doe",
         "userId":"8b081ef6-4792-4def-b2c9-c363a1bf41d5",
         "email":"kk.lim@gsretail.com",
         "tenantId":"6e5147da-6a35-4275-b3f3-fc069456b6eb",
         "visibleHistoryStartDateTime":"2019-04-18T23:51:43.255Z"
      },
      {
         "@odata.type":"#microsoft.graph.aadUserConversationMember",
         "id":"2de87aaf-844d-4def-9dee-2c317f0be1b3",
         "roles":[
            "owner"
         ],
         "displayName":"Bart Hogan",
         "userId":"2de87aaf-844d-4def-9dee-2c317f0be1b3",
         "email":"abc.def@gsretail.com",
         "tenantId":"6e5147da-6a35-4275-b3f3-fc069456b6eb",
         "visibleHistoryStartDateTime":"0001-01-01T00:00:00Z"
      },
      {
         "@odata.type":"#microsoft.graph.aadUserConversationMember",
         "id":"07ad17ad-ada5-4f1f-a650-7a963886a8a7",
         "roles":[
            "owner"
         ],
         "displayName":"Minna Pham",
         "userId":"07ad17ad-ada5-4f1f-a650-7a963886a8a7",
         "email":"ghi.hkl@gsretail.com",
         "tenantId":"6e5147da-6a35-4275-b3f3-fc069456b6eb",
         "visibleHistoryStartDateTime":"2019-04-18T23:51:43.255Z"
      }
   ]
}

7.2 멤버 제외

채팅창의 멤버정보를 조회 후 원하는 제외를 원하는 멤버의 email정보와 함게 제공되는 id 값으로 삭제를 진행합니다.

기본 형식

DELETE /chats/{chat-id}/members/{membership-id}

요청 예제

DELETE https://graph.microsoft.com/v1.0/chats/19:cf66807577b149cca1b7af0c32eec122@thread.v2/members/MCMjMjQzMmI1N2ItMGFiZC00M2RiLWFhN2ItMTZlYWRkMTE1ZDM0IyMxOTpiZDlkYTQ2MzIzYWY0MjUzOTZkMGZhNjcyMDAyODk4NEB0aHJlYWQudjIjIzQ4YmY5ZDUyLWRjYTctNGE1Zi04Mzk4LTM3Yjk1Y2M3YmQ4Mw==

응담 예제

HTTP/1.1 204 No Content

7. 마치며

현재 Teams graph api는 app이 처리 할수 있는 역할이 매우 제한적입니다.

기존에 workplace에서 사용하던 방식이 필요하여 고민하고 처리한 내용을 정리 했습니다.
teams를 사용하는 분들에게 도움이 되었으면 좋겠네요.

또한 Teams graph api에서 많은 기능을 제공하는 업데이트를 기대하고 있습니다.

참고자료

Posted by lahuman

댓글을 달아 주세요

화면 크기 만큼 이미지를 꽉 채우고 이미지가 클 경우 스크롤이 생기게 하는 예제

GO CODESENDBOX

이미지를 div tag로 감싸서 처리하면 쉽게 될 줄 알았는데 스크롤이 생기기 위해서는 height, width의 크기가 있어야 합니다.

작성한 예제는 top, left, right, bottom을 0으로 주어서 크기를 잡아 처리 했습니다.

작성한 예제

<body style="overflow: hidden;">
    <div
      style="
        overflow: auto;
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
      "
    >
      <img
        src="https://images.hdqwalls.com/wallpapers/rocks-water-body-4k-ax.jpg"
      />
    </div>
  </body>
Posted by lahuman

댓글을 달아 주세요

Error: Invalid hook call. Hooks can only be called inside of the body of a function component.

내가 만든 React 모듈rollup으로 빌드 후 사용할때 위와 같은 오류를 만났습니다.

원인으로 peerDependencies 설정을 하지 않아서 발생했습니다.

package.json에 다음과 같이 peerDependencies 를 설정하고 모듈을 배포 하면 해당 오류가 사라졌습니다.

  "peerDependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  },

제가 사용한 rollup.conf 파일은 아래와 같습니다.

import peerDepsExternal from "rollup-plugin-peer-deps-external";
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import typescript from "rollup-plugin-typescript2";
import postcss from "rollup-plugin-postcss";
import copy from "rollup-plugin-copy";
import sass from 'node-sass';
import autoprefixer from 'autoprefixer';
import url from 'rollup-plugin-url';
import { terser } from 'rollup-plugin-terser';

const packageJson = require("./package.json");

export default {
  // preserveModules: true,
  input: "src/index.ts",
  output: [
    {
      file: packageJson.main,
      // dir: 'build',
      format: "cjs",
      // preserveModules: true,
      sourcemap: false
    },
  ],
  plugins: [
    url({
      // by default, rollup-plugin-url will not handle font files
      include: ['**/*.woff', '**/*.woff2', '**/*.ttf'],
      // setting infinite limit will ensure that the files
      // are always bundled with the code, not copied to /dist
      limit: Infinity,
    }),
    peerDepsExternal(),
    resolve(),
    commonjs(),
    typescript({ useTsconfigDeclarationDir: true }),
    copy({
      targets: [
        { src: ['src/assets/theme/fonts/*'], dest: 'build/fonts' },
      ]
    }),
    postcss({
      preprocessor: (content, id) => new Promise((resolve, reject) => {
        const result = sass.renderSync({ file: id });
        resolve({ code: result.css.toString() })
      }),
      plugins: [
        autoprefixer(),
      ],
      sourceMap: false,
      minimize: true,
      extract: true,
      extensions: ['.sass', '.css']
    }),
    terser()
  ]
};
Posted by lahuman

댓글을 달아 주세요