Safari推送通知

时间:2013-10-31 13:43:57

标签: safari push-notification safari-push-notifications

我想为我的网站实现推送通知(显然只能在兼容的浏览器中作为Safari 7)。 我已经阅读了Apple文档,并且我已经成功创建了包含我的icon.iconset,我的certificate.p12,manifest.json和一个website.json的包。 现在,当我第一次访问该网站时,我想向用户请求许可。如果他允许的话,我应该发送包裹。 一切都很清楚,但我不知道如何继续下去。

如何从文件中创建推送包?我该如何精确签名?包应该总是一样的,所以我可以在我的Mac上签名并上传到我的服务器只有一个包。

如果您有使用此技术的经验,请告知我们:)

3 个答案:

答案 0 :(得分:3)

我已经成功创建了推送包,以便在java中使用REST API来获取有关safari web推送通知的权限。此外,我还遵循Apple官员在网站上提供的步骤。

请按照以下步骤创建推送包。

  1. 从您的苹果帐户创建您的网络推送通知P12证书。

  2. 使用https for pushPackage创建REST API,其中包含icon.iconset,您的certificate.p12,manifest.json和website.json。 p12证书必须是网络推送通知证书。

  3. 如何创建推送包: - 请参考下面的java代码。我使用java servlet创建了push包。提供2个Web服务端点。

  4. V1 / pushpackage / webpushID

  5. V1 /日志
  6. 处理通过safari推送通知方法发送的推送包请求的Servlet

    SafariPushPakageAPI.java / * 推送包REST API处理程序 * /

    import java.io.IOException;
    import java.io.OutputStream;
    import java.util.ArrayList;
    import java.util.Map;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.apache.commons.io.IOUtils;
    import org.apache.commons.lang.StringUtils;
    
    import com.safari.Packager;
    
    public class SafariPushPakageAPI extends HttpServlet{
    
        /**
         * 
         */
        private static final long serialVersionUID = 1L;
        public static  String ServerPath = null;
        private static final String REQUEST_PERMISSION = "/v1/pushPackages/YOUR_WEB_PUSH_ID";
        private static final String REQUEST_ERRORLOG = "/v1/log";
    
        @Override
        public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            doRequest(request, response);
        }
    //  /v1/pushPackages/webpushID
    //  /v1/log
        @Override
        public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            doRequest(request, response);
        }
    
        private void doRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            System.out.println("===>> SAFARI PUSH NOTIFICATION REQUEST");
            String path = request.getPathInfo();
            System.out.println("PATH ===>> "+path);
            if(path == null){
                doRequestPermission(request, response);
            }else if (path.equalsIgnoreCase(REQUEST_PERMISSION)){
                doRequestPermission(request, response);
            }else if (path.equalsIgnoreCase(REQUEST_ERRORLOG)){
                doRequestShowErrorLog(request, response);
            }else{
                doRequestPermission(request, response);
            }
        }
    
        private void doRequestPermission(HttpServletRequest request,HttpServletResponse response) {
            try{
                System.out.println("INSIDE REQUEST PERMISSION ==>>>");
                System.out.println(IOUtils.toString(request.getReader()));
                String authToken = StringUtils.isBlank(request.getParameter("token")) ? "UserTokenRT124DFGH" : StringUtils.trimToEmpty(request.getParameter("token"));
                System.out.println("=>>>>>>>>>> USER TOKEN =>>>>>>>>>> "+authToken);
                @SuppressWarnings("deprecation")
                String packagePath =request.getRealPath("pushPackage.raw/icon.iconset/"); // LOCATION WHERE YOUR PUSH PACKAGE FOLDER CONTAIN LOGOS AND website.json file
                response.setContentType("application/zip");
                response.setHeader("Content-Disposition", "attachment;filename=\"pushpackage.zip\"");
                OutputStream out = response.getOutputStream();
                out.write(Packager.createPackageFile(authToken,packagePath));
                response.flushBuffer();
            }catch(IOException ioe){
                ioe.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }   
        }
    
        private void doRequestShowErrorLog(HttpServletRequest request,HttpServletResponse response) {
            try{
                System.out.println("ERROR LOG STARTED");
                System.out.println(IOUtils.toString(request.getReader()));
                System.out.println("END");
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    
    }
    

    Packager.java

    import java.io.DataInputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    
    import org.apache.commons.io.IOUtils;
    import org.json.JSONArray;
    import org.json.JSONObject;
    
    /**
     *
     * @author Ritesh
     */
    public class Packager {
    
        final static String CERTIFICATE_PATH="PATH TO YOUR 12 CERTIFICATE";
        final static String CERTIFICATE_PASS="PASSWORD";
    
        static String getJSON(String authenticationToken) throws Exception {
            JSONObject obj = new JSONObject();
            obj.put("websiteName", "WEB SITE NAME");
            obj.put("websitePushID", "WEB PUSH ID");
            obj.put("allowedDomains", new JSONArray());
    
            obj.getJSONArray("allowedDomains").put("https://TEST.EXAMPLE.net");//LIST OF DOMAINS ALLOW
    
            obj.put("urlFormatString", "https://TEST.EXAMPLE.net/%@");
            obj.put("authenticationToken", authenticationToken);
            obj.put("webServiceURL", "https://API.EXAMPLE.COM");//callback URL WITHOUT WEB SERVICE ENDPOINT NAME
    
            return obj.toString();
        }
    
        public static byte[] createPackageFile(String authenticationToken, String path) throws Exception {
    
            System.out.println("packaging safari file with token: " + authenticationToken);
            ZipHandler zip = new ZipHandler();
            File dir = new File(path);
    
            for (File file : dir.listFiles()) {          
                 InputStream is = new FileInputStream(file);
                 byte[] bytes = IOUtils.toByteArray(is);
                 zip.addFile("icon.iconset", file.getName(),bytes );
            }       
    
            zip.addFile("", "website.json", getJSON(authenticationToken).getBytes());
    
            byte[] manifest = zip.manifest();
            zip.addFile("", "manifest.json", manifest);
    
            zip.addFile("", "signature", sign(manifest));
    
            return zip.getBytes();
    
        }
    
        static byte[] sign(byte bytesToSign[]) throws Exception {
            return new PKCS7Signer().sign(CERTIFICATE_PATH,CERTIFICATE_PASS, bytesToSign);
        }
    
        /**
         * Servlet handler , should listen on the callback URL (as in webServiceURL)
         * @param requestPath
         * @param req
         * @param servletRequest
         * @param servletResponse
         * @throws Exception
         */
    
    
        public static void main(String[] args) throws Exception {
            Packager.createPackageFile("SafriNotifcation","");
        }               
    
    }
    

    创建签名文件的PKCS7Signer.java。

    import java.io.FileInputStream;
    import java.security.KeyStore;
    import java.security.PrivateKey;
    import java.security.Security;
    import java.util.ArrayList;
    import java.util.Enumeration;
    import java.util.List;
    
    import org.bouncycastle.cert.X509CertificateHolder;
    import org.bouncycastle.cert.jcajce.JcaCertStore;
    import org.bouncycastle.cms.CMSProcessableByteArray;
    import org.bouncycastle.cms.CMSSignedData;
    import org.bouncycastle.cms.CMSSignedDataGenerator;
    import org.bouncycastle.cms.CMSTypedData;
    import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
    import org.bouncycastle.jce.provider.BouncyCastleProvider;
    import org.bouncycastle.operator.ContentSigner;
    import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
    import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
    import org.bouncycastle.util.Store;
    
    public final class PKCS7Signer {
    
        static {
            try{
                Security.addProvider(new BouncyCastleProvider());
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    
        private KeyStore getKeystore(String storeLocation, String storePasswd) throws Exception {
            if (storeLocation == null) {
                System.out.println("Could not find store file (.p12)");
                return null;
            }
            // First load the keystore object by providing the p12 file path
            KeyStore clientStore = KeyStore.getInstance("PKCS12");
            // replace testPass with the p12 password/pin
            clientStore.load(new FileInputStream(storeLocation), storePasswd.toCharArray());
            return clientStore;
        }
    
        private X509CertificateHolder getCert(KeyStore keystore, String alias) throws Exception {
            java.security.cert.Certificate c = keystore.getCertificate(alias);
            return new X509CertificateHolder(c.getEncoded());
        }
    
        private PrivateKey getPrivateKey(KeyStore keystore, String alias, String storePasswd) throws Exception {
            return (PrivateKey) keystore.getKey(alias, storePasswd.toCharArray());
        }
    
        public byte[] sign(String storeLocation, String storePasswd, byte[] dataToSign) throws Exception {
            KeyStore clientStore = getKeystore(storeLocation, storePasswd);
    
            if (clientStore == null) {
                return null;
            }
            Enumeration aliases = clientStore.aliases();
            String alias = "";
            while (aliases.hasMoreElements()) {
                alias = (String) aliases.nextElement();
                if (clientStore.isKeyEntry(alias)) {
                    break;
                }
            }
    
            CMSTypedData msg = new CMSProcessableByteArray(dataToSign); // Data to sign
    
            X509CertificateHolder x509Certificate = getCert(clientStore, alias);
            List certList = new ArrayList();
            certList.add(x509Certificate); // Adding the X509 Certificate
    
            Store certs = new JcaCertStore(certList);
    
            CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
            // Initializing the the BC's Signer
            ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(
                    getPrivateKey(clientStore, alias, storePasswd));
    
            gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder()
                    .setProvider("BC").build()).build(sha1Signer, x509Certificate));
            // adding the certificate
            gen.addCertificates(certs);
            // Getting the signed data
            CMSSignedData sigData = gen.generate(msg, false);
            return sigData.getEncoded();
        }
    }
    

    请注意使用最新的充气城堡罐来创建签名文件 bcprov-jdk15on-157.jar bcpkix-jdk15on-157.jar

    1. 在您的客户端页面上编写您的javascript。它包含简单的java脚本以获取权限。
    2. ///

      //对于safari var domain =“你的WEB PUSH ID”;

      function safariIniti() {
      
          var pResult = window.safari.pushNotification.permission(domain);
      
          if(pResult.permission === 'default') {
              //request permission
              requestPermissions();
          } else if (pResult.permission === 'granted') {
              console.log("Permission for " + domain + " is " + pResult.permission);
              var token = pResult.deviceToken;
              // Show subscription for debug
              console.log('Subscription details:'+token);
          } else if(pResult.permission === 'denied') {
              console.log("Permission for " + domain + " is " + pResult.permission);
          }
      }
      
      function getToken(){
      
          // always start with a letter (for DOM friendlyness)
          var idstr=String.fromCharCode(Math.floor((Math.random()*25)+65));
          do {                
              // between numbers and characters (48 is 0 and 90 is Z (42-48 = 90)
              var ascicode=Math.floor((Math.random()*42)+48);
              if (ascicode<58 || ascicode>64){
                  // exclude all chars between : (58) and @ (64)
                  idstr+=String.fromCharCode(ascicode);    
              }                
          } while (idstr.length<32);
      
          return (idstr);
      }
      
      
      function requestPermissions() {
      
          var tokenVal = getToken();
          window.safari.pushNotification.requestPermission('WEb service url without end points',domain,{token:tokenVal},
      function(subscription) {
      
      
              console.log(subscription.permission);
              console.log("PERMISSION ====>> "+subscription.permission);
              if(subscription.permission === 'granted') {
                  //TODO
              }
              else if(subscription.permission === 'denied') {
                  // TODO:
              }
          });
      
      }
      

答案 1 :(得分:2)

Apple提供了一个php文件,您可以使用该文件创建包含签名的推送包。 https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NotificationProgrammingGuideForWebsites/CompanionFile.zip

或者,您可以使用我们在为zeropush.com实施safari推送通知时开发的push_package gem https://github.com/SymmetricInfinity/push_package。有关详细信息,请访问https://zeropush.com/blog/implementing-safari-push-notifications-in-osx-mavericks

答案 2 :(得分:0)

按照 apple documentation github repo ,它们包含创建Safari推送通知所需的足够信息。